pull/32/head
Suvodip 2025-04-11 12:59:43 +05:30
parent ee2bbc817f
commit 5efa3d3270
15 changed files with 915 additions and 328 deletions

49
package-lock.json generated
View File

@ -25,6 +25,7 @@
"lucide-react": "^0.484.0", "lucide-react": "^0.484.0",
"pocketbase": "^0.25.2", "pocketbase": "^0.25.2",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"react-qr-code": "^2.0.15",
"react-router-dom": "^7.4.1", "react-router-dom": "^7.4.1",
"react-to-print": "^3.0.5", "react-to-print": "^3.0.5",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",
@ -4524,6 +4525,18 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@ -6009,6 +6022,17 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/property-information": { "node_modules/property-information": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz",
@ -6019,6 +6043,12 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/qr.js": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz",
"integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==",
"license": "MIT"
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -6068,6 +6098,25 @@
"react": "^19.0.0" "react": "^19.0.0"
} }
}, },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-qr-code": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.15.tgz",
"integrity": "sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1",
"qr.js": "0.0.0"
},
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.14.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",

View File

@ -27,6 +27,7 @@
"lucide-react": "^0.484.0", "lucide-react": "^0.484.0",
"pocketbase": "^0.25.2", "pocketbase": "^0.25.2",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"react-qr-code": "^2.0.15",
"react-router-dom": "^7.4.1", "react-router-dom": "^7.4.1",
"react-to-print": "^3.0.5", "react-to-print": "^3.0.5",
"tailwind-merge": "^3.0.2", "tailwind-merge": "^3.0.2",

View File

@ -1,7 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Toast } from './Toast'; import { Toast } from './Toast';
import { TemplatePreview } from './TemplatePreview'; import { TemplatePreview } from './TemplatePreview';
import { Button } from './ui/button';
import { Input } from "./ui/input";
import { Label, Select } from '@radix-ui/react-select';
export const DomainSetupForm = ({ defaultSubdomain }) => { export const DomainSetupForm = ({ defaultSubdomain }) => {
// Deployment type and app selections // Deployment type and app selections
const [deploymentType, setDeploymentType] = useState('app'); const [deploymentType, setDeploymentType] = useState('app');
@ -27,12 +29,73 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
// DNS configuration // DNS configuration
const [dnsMethod, setDnsMethod] = useState('cname'); const [dnsMethod, setDnsMethod] = useState('cname');
const [showDnsConfig, setShowDnsConfig] = useState(false); const [showDnsConfig, setShowDnsConfig] = useState(false);
const [dnsVerified, setDnsVerified] = useState({ const [dnsVerified, setDnsVerified] = useState({ cname: false, ns: false, a: false, ip: false});
cname: false, const [txnId, setTxnId] = useState('');
ns: false, const [userEmail, setUserEmail] = useState('');
a: false,
ip: false const [panelType, setPanelType] = useState('');
const API_URL = 'http://192.168.1.197:2058/v1/users/index.php';
const SERVICES_API_URL = 'http://192.168.1.197:2058/v1/services/index.php';
// const BILLING_API_URL = 'http://192.168.1.197:2058/v1/users/index.php';
const [selectedTenure, setSelectedTenure] = useState('');
const [selectedPrice, setSelectedPrice] = useState(0);
const handleCheckboxChange = (tenure, price) => {
if (selectedTenure === tenure) {
setSelectedTenure('');
setSelectedPrice(0);
} else {
setSelectedTenure(tenure);
setSelectedPrice(price);
}
// console.log(selectedTenure, ' ', selectedPrice);
};
const handlePanelBuyNow = () => {
// Disable button during processing
const buyButton = document.getElementById('buy-button'); // Add ID to your button
if (buyButton) buyButton.disabled = true;
showToast('Loading...');
const formData = new FormData();
formData.append('service', panelType);
formData.append('tenure', selectedTenure);
formData.append('amount', 1); //selectedPrice
fetch(`${API_URL}?query=initiate_payment`, {
method: 'POST',
body: formData,
credentials: 'include'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if(data.success === true){
setTxnId(data.txn_id);
setUserEmail(data.user_email);
window.location.href = `/make-payment?query=get-initiated_payment&orderId=${data.order_id}`
// redirectToPayU(data);
showToast('Redirecting to payment page...');
} else {
throw new Error(data.message || 'Payment initialization failed');
}
})
.catch(error => {
showToast(error.message || 'Payment failed. Please try again.');
console.error('An error occurred:', error);
})
.finally(() => {
if (buyButton) buyButton.disabled = false;
}); });
};
// Form validation // Form validation
const [formValid, setFormValid] = useState(true); const [formValid, setFormValid] = useState(true);
@ -159,52 +222,71 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
}; };
// Validate domain // Validate domain
const validateDomain = () => { const validateDomain = async () => {
const domain = domainType === 'domain' ? customDomain : customSubdomain; const domain = domainType === 'domain' ? customDomain : customSubdomain;
if (!domain) { // Reset validation state
setValidationMessage('Please enter a domain name.');
setIsValidDomain(false);
return;
}
// Initial validation: check format with regex
const validFormat = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/.test(domain);
if (!validFormat) {
setValidationMessage('Domain format is invalid. Please check your entry.');
setIsValidDomain(false);
return;
}
setIsValidating(true); setIsValidating(true);
setIsValidDomain(false);
setValidationMessage(''); setValidationMessage('');
setShowDnsConfig(false); setShowDnsConfig(false);
// Simulate an API call to validate the domain // Check if domain is empty
setTimeout(() => { if (!domain) {
// Simulate a real domain check - in a real app this would be an API call setValidationMessage('Please enter a domain name.');
// call /host-api/v1/domains/validate/?domain=domain.com
const checkResult = true; // Assume domain is valid for demo
setIsValidating(false); setIsValidating(false);
setIsValidDomain(checkResult); setIsValidDomain(false);
return;
if (checkResult) {
setValidationMessage('Domain is valid and registered.');
setShowDnsConfig(true);
} else {
setValidationMessage('Domain appears to be unregistered or unavailable.');
} }
// Validate domain format
const domainFormatRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i;
if (!domainFormatRegex.test(domain)) {
setValidationMessage('Domain format is invalid. Please check your entry.');
setIsValidating(false);
setIsValidDomain(false);
return;
}
try {
// Make API call to validate domain
const response = await fetch(`${SERVICES_API_URL}?query=validate-domain`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ domain })
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (data.valid) {
setValidationMessage('Domain is valid and registered.');
setIsValidDomain(true);
setShowDnsConfig(true);
} else {
setValidationMessage(data.message || 'Domain appears to be unregistered or unavailable.');
setIsValidDomain(false);
}
} catch (error) {
console.error('Domain validation error:', error);
setValidationMessage('Error validating domain. Please try again.');
setIsValidDomain(false);
} finally {
setIsValidating(false);
validateForm(); validateForm();
}, 500); }
}; };
// Check DNS configuration // Check DNS configuration
const checkSubDomainCname = () => { const checkSubDomainCname = () => {
const domainToCheck = customDomain || customSubdomain; const domainToCheck = customDomain || customSubdomain;
fetch('http://localhost:2058/host-api/v1/check-c-name/', { fetch(`${SERVICES_API_URL}?query=check-c-name`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
@ -312,6 +394,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
<option value="source"> From Source</option> <option value="source"> From Source</option>
<option value="static">📄 Static Site Upload</option> <option value="static">📄 Static Site Upload</option>
<option value="sample-web-app">🌐 Sample Web App</option> <option value="sample-web-app">🌐 Sample Web App</option>
<option value="php-mysql-with-admin-panel">🗄 PHP MYSQL with a admin panel</option>
</select> </select>
</div> </div>
@ -461,7 +544,74 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
</div> </div>
</div> </div>
)} )}
{
deploymentType === 'php-mysql-with-admin-panel' && (
<div className="space-y-2">
<label htmlFor="app-type" className="block text-white font-medium">Select Panel</label>
<select
id="app-type"
value={panelType}
onChange={(e) => setPanelType(e.target.value)}
className="w-full rounded-md py-2 px-3 bg-neutral-700 border border-neutral-600 text-white focus:outline-none focus:ring-2 focus:ring-[#6d9e37]"
>
<option value="">-Select-</option>
<option value="Hestia-Panel">🧰 Hestia Panel</option>
<option value="Webmin">🖥 Webmin</option>
<option value="cPanel">📊 cPanel</option>
</select>
</div>
)
}
{
deploymentType === 'php-mysql-with-admin-panel' && panelType === 'Hestia-Panel' && (
<>
<ul className="flex justify-between text-sm">
{["5 Domains", "free Let's Encrypt SSL", "1 MariaDB database", "phpMyAdmin"].map((feature, index) => (
<li key={index} className="flex items-start">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 text-[#6d9e37] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" ><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /></svg>
<span className='text-zinc-400'>{feature}</span>
</li>
))}
</ul>
<div className='flex flex-row justify-between items-center gap-x-6'>
<label className={`border ${selectedTenure === 'monthly' ? 'bg-[#6d9e37]' : ''} border-[#6d9e37] text-white px-4 py-2 text-center rounded-md w-full cursor-pointer`}>
<input type="checkbox" checked={selectedTenure === 'monthly'} onChange={() => handleCheckboxChange('monthly', 200)} className="hidden" />
<p className='text-3xl font-bold text-center'>200</p>
<span>Monthly</span>
</label>
<label className={`border ${selectedTenure === 'yearly' ? 'bg-[#6d9e37]' : ''} border-[#6d9e37] text-white px-4 py-2 text-center rounded-md w-full cursor-pointer`}>
<input type="checkbox" checked={selectedTenure === 'yearly'} onChange={() => handleCheckboxChange('yearly', 2000)} className="hidden" />
<p className='text-3xl font-bold text-center'>2000</p>
<span>Yearly</span>
</label>
</div>
<div className='text-white'>
{selectedTenure && (
<p className={`${selectedTenure === 'monthly' ? 'text-left' : 'text-end'}`}>You selected <strong>{selectedTenure}</strong> plan at {selectedPrice}</p>
)}
</div>
</>
)
}
{
deploymentType === 'php-mysql-with-admin-panel' && panelType === 'cPanel' ? (
<div>
<p>
cPanel is a proprietary software. Here at Siliconpin, we encourage using freedom-oriented software.
If you need a cPanel, you can visit &nbsp;
<a className='text-[#6d9e37]' href="https://cicdhosting.com" target='_blank'>https://cicdhosting.com</a>
</p>
</div>
) : deploymentType === 'php-mysql-with-admin-panel' && (
<Button onClick={handlePanelBuyNow} className='w-full'>Proceed to Pay</Button>
)
}
{
deploymentType !== 'php-mysql-with-admin-panel' && (
<>
{/* Domain Configuration */} {/* Domain Configuration */}
<div className="pt-4 border-t border-neutral-700"> <div className="pt-4 border-t border-neutral-700">
<h3 className="text-lg font-medium text-white mb-4">Destination</h3> <h3 className="text-lg font-medium text-white mb-4">Destination</h3>
@ -469,13 +619,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
<div className="space-y-4"> <div className="space-y-4">
{/* SiliconPin Subdomain */} {/* SiliconPin Subdomain */}
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<input <input type="checkbox" id="use-subdomain" checked={useSubdomain} onChange={handleUseSubdomainChange} className="mt-1 accent-[#6d9e37]" />
type="checkbox"
id="use-subdomain"
checked={useSubdomain}
onChange={handleUseSubdomainChange}
className="mt-1"
/>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="use-subdomain" className="block text-white font-medium">Use SiliconPin Subdomain</label> <label htmlFor="use-subdomain" className="block text-white font-medium">Use SiliconPin Subdomain</label>
<div className="mt-2 flex"> <div className="mt-2 flex">
@ -493,13 +637,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
{/* Custom Domain */} {/* Custom Domain */}
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<input <input type="checkbox" id="use-custom-domain" checked={useCustomDomain} onChange={handleUseCustomDomainChange} className="mt-1 accent-[#6d9e37]" />
type="checkbox"
id="use-custom-domain"
checked={useCustomDomain}
onChange={handleUseCustomDomainChange}
className="mt-1"
/>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="use-custom-domain" className="block text-white font-medium">Use Custom Domain</label> <label htmlFor="use-custom-domain" className="block text-white font-medium">Use Custom Domain</label>
@ -508,28 +646,12 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
{/* Domain Type Selection */} {/* Domain Type Selection */}
<div className="flex flex-col space-y-3 sm:flex-row sm:space-y-0 sm:space-x-4"> <div className="flex flex-col space-y-3 sm:flex-row sm:space-y-0 sm:space-x-4">
<div className="flex items-center"> <div className="flex items-center">
<input <input type="radio" id="domain-type-domain" name="domain-type" value="domain" checked={domainType === 'domain'} onChange={handleDomainTypeChange} className="mr-2 accent-[#6d9e37]" />
type="radio"
id="domain-type-domain"
name="domain-type"
value="domain"
checked={domainType === 'domain'}
onChange={handleDomainTypeChange}
className="mr-2"
/>
<label htmlFor="domain-type-domain" className="text-white">Root Domain</label> <label htmlFor="domain-type-domain" className="text-white">Root Domain</label>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<input <input type="radio" id="domain-type-subdomain" name="domain-type" value="subdomain" checked={domainType === 'subdomain'} onChange={handleDomainTypeChange} className="mr-2 accent-[#6d9e37]"/>
type="radio"
id="domain-type-subdomain"
name="domain-type"
value="subdomain"
checked={domainType === 'subdomain'}
onChange={handleDomainTypeChange}
className="mr-2"
/>
<label htmlFor="domain-type-subdomain" className="text-white">Subdomain</label> <label htmlFor="domain-type-subdomain" className="text-white">Subdomain</label>
</div> </div>
</div> </div>
@ -599,17 +721,9 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
<h4 className="text-white font-medium">Connect Your Domain</h4> <h4 className="text-white font-medium">Connect Your Domain</h4>
{/* CNAME Record Option */} {/* CNAME Record Option */}
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3"> <div className={`p-4 bg-neutral-700/30 rounded-md space-y-3 ${dnsMethod === 'cname' ? 'border-2 border-[#6d9e37]' : 'border border-neutral-600'}`}>
<label for="dns-cname" className="flex items-start cursor-pointer"> <label for="dns-cname" className={`flex items-start cursor-pointer`}>
<input <input type="radio" id="dns-cname" name="dns-method" value="cname" checked={dnsMethod === 'cname'} onChange={handleDnsMethodChange} className="mt-1 mr-2 accent-[#6d9e37]" />
type="radio"
id="dns-cname"
name="dns-method"
value="cname"
checked={dnsMethod === 'cname'}
onChange={handleDnsMethodChange}
className="mt-1 mr-2"
/>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="dns-cname" className="block text-white font-medium">Use CNAME Record</label> <label htmlFor="dns-cname" className="block text-white font-medium">Use CNAME Record</label>
<p className="text-sm text-neutral-300">Point your domain to our SiliconPin subdomain</p> <p className="text-sm text-neutral-300">Point your domain to our SiliconPin subdomain</p>
@ -646,17 +760,9 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
{/* Nameserver Option (only for full domains, not subdomains) */} {/* Nameserver Option (only for full domains, not subdomains) */}
{domainType === 'domain' && ( {domainType === 'domain' && (
<> <>
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3"> <div className={`p-4 bg-neutral-700/30 rounded-md space-y-3 ${dnsMethod === 'ns' ? 'border-2 border-[#6d9e37]' : 'border border-neutral-600'}`}>
<label for="dns-ns" className="flex items-start cursor-pointer"> <label for="dns-ns" className="flex items-start cursor-pointer">
<input <input type="radio" id="dns-ns" name="dns-method" value="ns" checked={dnsMethod === 'ns'} onChange={handleDnsMethodChange} className="mt-1 mr-2 accent-[#6d9e37]" />
type="radio"
id="dns-ns"
name="dns-method"
value="ns"
checked={dnsMethod === 'ns'}
onChange={handleDnsMethodChange}
className="mt-1 mr-2"
/>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="dns-ns" className="block text-white font-medium">Use Our Nameservers</label> <label htmlFor="dns-ns" className="block text-white font-medium">Use Our Nameservers</label>
<p className="text-sm text-neutral-300">Update your domain's nameservers to use ours</p> <p className="text-sm text-neutral-300">Update your domain's nameservers to use ours</p>
@ -664,24 +770,14 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
<div className="mt-3 space-y-2"> <div className="mt-3 space-y-2">
<div className="flex items-center"> <div className="flex items-center">
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns1.siliconpin.com</div> <div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns1.siliconpin.com</div>
<button <button type="button" onClick={() => copyToClipboard('ns1.siliconpin.com')} className="ml-2 text-[#6d9e37] hover:text-white" aria-label="Copy nameserver value">
type="button"
onClick={() => copyToClipboard('ns1.siliconpin.com')}
className="ml-2 text-[#6d9e37] hover:text-white"
aria-label="Copy nameserver value"
>
<span className="text-lg">📋</span> <span className="text-lg">📋</span>
</button> </button>
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns2.siliconpin.com</div> <div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns2.siliconpin.com</div>
<button <button type="button" onClick={() => copyToClipboard('ns2.siliconpin.com')} className="ml-2 text-[#6d9e37] hover:text-white" aria-label="Copy nameserver value">
type="button"
onClick={() => copyToClipboard('ns2.siliconpin.com')}
className="ml-2 text-[#6d9e37] hover:text-white"
aria-label="Copy nameserver value"
>
<span className="text-lg">📋</span> <span className="text-lg">📋</span>
</button> </button>
</div> </div>
@ -703,30 +799,16 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
</label> </label>
</div> </div>
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3"> <div className={`p-4 bg-neutral-700/30 rounded-md space-y-3 ${dnsMethod === 'ip' ? 'border-2 border-[#6d9e37]' : 'border border-neutral-600'}`}>
<label for="dns-ip" className="flex items-start cursor-pointer"> <label for="dns-ip" className="flex items-start cursor-pointer">
<input <input type="radio" id="dns-ip" name="dns-method" value="ip" checked={dnsMethod === 'ip'} onChange={handleDnsMethodChange} className="mt-1 mr-2 accent-[#6d9e37]" />
type="radio"
id="dns-ip"
name="dns-method"
value="ip"
checked={dnsMethod === 'ip'}
onChange={handleDnsMethodChange}
className="mt-1 mr-2"
/>
<div className="flex-1"> <div className="flex-1">
<label htmlFor="dns-ip" className="block text-white font-medium">Use Our IP Address</label> <label htmlFor="dns-ip" className="block text-white font-medium">Use Our IP Address</label>
<p className="text-sm text-neutral-300">Update your domain's nameservers to use ours</p> <p className="text-sm text-neutral-300">Update your domain's nameservers to use ours</p>
<div className="mt-3 space-y-2"> <div className="mt-3 space-y-2">
<div className="flex items-center"> <div className="flex items-center">
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">xxx.xxx.x.xx</div> <div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">xxx.xxx.x.xx</div>
<button <button type="button" onClick={() => copyToClipboard('xxx.xxx.x.xx')} className="ml-2 text-[#6d9e37] hover:text-white" aria-label="Copy nameserver value">
type="button"
onClick={() => copyToClipboard('xxx.xxx.x.xx')}
className="ml-2 text-[#6d9e37] hover:text-white"
aria-label="Copy nameserver value"
>
<span className="text-lg">📋</span> <span className="text-lg">📋</span>
</button> </button>
</div> </div>
@ -758,7 +840,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
</div> </div>
</div> </div>
{/* Form Submit Button */} {/* Form Submit Button */}
<button <Button
type="submit" type="submit"
disabled={useCustomDomain && !formValid} disabled={useCustomDomain && !formValid}
className={`w-full mt-6 px-6 py-3 text-white font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-neutral-800 className={`w-full mt-6 px-6 py-3 text-white font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-neutral-800
@ -768,9 +850,15 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
}`} }`}
> >
Start the Deployment Start the Deployment
</button> </Button>
</>
)
}
</form> </form>
<Toast visible={toast.visible} message={toast.message} /> <Toast visible={toast.visible} message={toast.message} />
</div> </div>
); );
}; };
// upi://pay?pa=merchant@bank&pn=Merchant%20Inc&am=100.00&cu=INR&tn=Payment%20for%20goods

View File

@ -28,8 +28,8 @@ interface AuthResponse {
const LoginPage = () => { const LoginPage = () => {
const [email, setEmail] = useState('suvodip@siliconpin.com'); const [email, setEmail] = useState('');
const [password, setPassword] = useState('Simple2pass'); const [password, setPassword] = useState('');
const [passwordVisible, setPasswordVisible] = useState(false); const [passwordVisible, setPasswordVisible] = useState(false);
const [status, setStatus] = useState<AuthStatus>({ message: '', isError: false }); const [status, setStatus] = useState<AuthStatus>({ message: '', isError: false });
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -118,7 +118,7 @@ const LoginPage = () => {
const syncSessionWithBackend = async (authData: AuthResponse, avatarUrl: string) => { const syncSessionWithBackend = async (authData: AuthResponse, avatarUrl: string) => {
try { try {
const response = await fetch('http://localhost:2058/host-api/v1/users/session/', { const response = await fetch('http://192.168.1.197:2058/v1/users/?query=login', {
method: 'POST', method: 'POST',
credentials: 'include', // Important for cookies credentials: 'include', // Important for cookies
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },

View File

@ -0,0 +1,180 @@
import React, {useState, useEffect} from "react";
import { Button } from "./ui/button";
import QRCode from "react-qr-code";
import { Dialog, DialogContent, DialogHeader, DialogTitle} from "./ui/dialog";
import { Toast } from './Toast';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "./ui/card";
const API_URL = 'http://192.168.1.197:2058/v1/users/index.php';
export default function MakePayment(){
const [initialOrderData, setInitialOrderData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [showQRModal, setShowQRModal] = useState(false);
const [upiPaymentLink, setUpiPaymentLink] = useState("");
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const orderId = urlParams.get('orderId');
if (!orderId) {
setError('Order ID is missing from URL');
setIsLoading(false);
return;
}
const getInitialOrderData = () => {
setIsLoading(true);
const formData = new FormData();
formData.append('order_id', orderId);
fetch(`${API_URL}?query=get-initiated_payment`, {
method: 'POST',
body: formData,
credentials: 'include'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (!data.success) {
throw new Error(data.message || 'Failed to initialize payment');
}
setInitialOrderData(data);
setError(null);
// Generate UPI payment link when data is loaded
if (data.payment_data?.amount) {
const amount = data.payment_data.amount;
const upiLink = generateUPILink(amount, data.txn_id);
setUpiPaymentLink(upiLink);
}
})
.catch(error => {
setError(error.message || 'Payment failed. Please try again.');
console.error('An error occurred:', error);
})
.finally(() => {
setIsLoading(false);
});
};
getInitialOrderData();
}, []);
function generateUPILink(amount, transactionId) {
// Replace these with your actual merchant details
const merchantUPI = "siliconpin@ybl";
const merchantName = "SiliconPin";
const currency = "INR";
// Encode parameters for URL
const encodedMerchantName = encodeURIComponent(merchantName);
const transactionNote = encodeURIComponent(`Payment for order #${transactionId}`);
// Construct UPI payment link
return `upi://pay?pa=${merchantUPI}&pn=${encodedMerchantName}&am=${amount}&cu=${currency}&tn=${transactionNote}`;
}
// waiting payment update
// Payment update not recieved if
function redirectToPayU() {
if (!initialOrderData?.payment_data || !initialOrderData.payment_url) {
console.error('Payment data not loaded yet');
alert('Payment information is not ready. Please wait.');
return;
}
// Create a form dynamically
const form = document.createElement('form');
form.method = 'POST';
form.action = initialOrderData.payment_url;
form.style.display = 'none';
Object.entries(initialOrderData.payment_data).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
function handleQRPaymentClick() {
if (!upiPaymentLink) {
alert('Payment information is not ready. Please wait.');
return;
}
setShowQRModal(true);
}
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>;
}
if (error) {
return <div className="flex items-center justify-center min-h-screen">
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
<strong>Error:</strong> {error}
</div>
</div>;
}
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<Card className="max-w-md w-full bg-white p-6 rounded-lg shadow-md">
<h1 className="text-2xl font-bold mb-4">Complete Your Payment</h1>
{initialOrderData && (
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
<div className="flex justify-between mb-2">
<span className="font-bold">Order ID:</span>
<span>{initialOrderData.txn_id}</span>
</div>
<div className="flex justify-between">
<span className="font-bold">Amount:</span>
<span>{initialOrderData.payment_data?.amount}</span>
</div>
</div>
)}
<div className="flex flex-col">
<span className=""><strong>Pay Using:</strong></span>
<Button variant="outline" className="" onClick={handleQRPaymentClick} >UPI QR</Button>
<span className="text-gray-500 text-center font-semibold my-2">OR</span>
<Button className="" onClick={redirectToPayU} >Payment Gateway</Button>
<span className="text-center mt-2 text-gray-400 text-xs">Applicabel 2% Transaction Charge if using Payment Gateway</span>
</div>
</Card>
{/* QR Code Modal */}
<Dialog open={showQRModal} onOpenChange={setShowQRModal}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Scan QR Code to Pay</DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center space-y-4">
<div className="p-4 bg-white rounded-lg border border-gray-200">
<QRCode value={upiPaymentLink} size={256} level="H" bgColor="#ffffff" fgColor="#000000" />
</div>
<p className="text-sm text-gray-500 text-center">
Scan this QR code with any UPI app to complete your payment
</p>
<div className="w-full p-3 bg-gray-100 text-gray-500 rounded-md break-all text-xs">
{upiPaymentLink}
</div>
<Button variant="outline" onClick={() => navigator.clipboard.writeText(upiPaymentLink)} className="text-sm" >Copy UPI Link</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
}

View File

171
src/components/TestCool.jsx Normal file
View File

@ -0,0 +1,171 @@
import React, { useState } from "react";
export default function DeployWordPress() {
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState("");
const [debugInfo, setDebugInfo] = useState(null);
// Configuration - Replace with your actual values
const COOLIFY_API_URL = "http://192.168.1.197:8000/api/v1";
const TOKEN = "zXSR33z74eK26abbKyL9bz4d3PYouTSK8FSjOltv719c52d8";
const PROJECT_UUID = "wc40gg048gkwg0go80ggog44";
const SERVER_UUID = "sgswssowscc84o8sc8wockgc";
const ENVIRONMENT_NAME = "production"; // Changed from 'dev' to 'production' as default
const createWordPress = async () => {
setLoading(true);
setMessage("");
setDebugInfo(null);
try {
// 1. Create MySQL Service - Updated to Coolify's expected format
const mysqlPayload = {
name: "wordpress-db",
type: "mysql",
projectUuid: PROJECT_UUID,
serverUuid: SERVER_UUID,
version: "8.0",
destination: { // Coolify often requires this structure
serverUuid: SERVER_UUID,
environment: ENVIRONMENT_NAME
},
configuration: {
type: "mysql",
settings: { // Changed from environmentVariables to settings
MYSQL_ROOT_PASSWORD: "example",
MYSQL_DATABASE: "wordpress",
MYSQL_USER: "wordpress",
MYSQL_PASSWORD: "example",
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
}
}
};
const mysqlRes = await fetch(`${COOLIFY_API_URL}/services`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(mysqlPayload),
});
const mysqlData = await mysqlRes.json();
setDebugInfo({ mysql: { request: mysqlPayload, response: mysqlData } });
if (!mysqlRes.ok) {
throw new Error(
mysqlData.message ||
mysqlData.error?.message ||
JSON.stringify(mysqlData.errors) ||
"MySQL validation failed"
);
}
// 2. Create WordPress Service
const wpPayload = {
name: "wordpress",
type: "wordpress",
projectUuid: PROJECT_UUID,
serverUuid: SERVER_UUID,
version: "latest",
destination: {
serverUuid: SERVER_UUID,
environment: ENVIRONMENT_NAME
},
configuration: {
type: "wordpress",
settings: {
WORDPRESS_DB_HOST: "wordpress-db",
WORDPRESS_DB_USER: "wordpress",
WORDPRESS_DB_PASSWORD: "example",
WORDPRESS_DB_NAME: "wordpress",
WORDPRESS_TABLE_PREFIX: "wp_"
}
}
};
const wpRes = await fetch(`${COOLIFY_API_URL}/services`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(wpPayload),
});
const wpData = await wpRes.json();
setDebugInfo(prev => ({ ...prev, wordpress: { request: wpPayload, response: wpData } }));
if (!wpRes.ok) {
throw new Error(
wpData.message ||
wpData.error?.message ||
JSON.stringify(wpData.errors) ||
"WordPress validation failed"
);
}
setMessage("🎉 WordPress + MySQL deployed successfully!");
} catch (err) {
setMessage(`❌ Deployment failed: ${err.message}`);
console.error("Deployment error:", err, debugInfo);
} finally {
setLoading(false);
}
};
return (
<div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md">
<h2 className="text-2xl font-bold mb-4">Deploy WordPress</h2>
<button
onClick={createWordPress}
disabled={loading}
className={`w-full py-2 px-4 rounded-md text-white font-medium ${
loading ? "bg-gray-400 cursor-not-allowed" : "bg-blue-600 hover:bg-blue-700"
}`}
>
{loading ? (
<span className="flex items-center justify-center">
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Deploying...
</span>
) : "Deploy WordPress"}
</button>
{message && (
<div className={`mt-4 p-3 rounded-md ${
message.includes("❌") ? "bg-red-50 text-red-700" : "bg-green-50 text-green-700"
}`}>
{message}
</div>
)}
{debugInfo && (
<div className="mt-4 space-y-4">
<details className="bg-gray-50 rounded-md p-2">
<summary className="font-medium cursor-pointer">Debug Details</summary>
<div className="mt-2 bg-white p-2 rounded border border-gray-200 overflow-auto max-h-60">
<pre>{JSON.stringify(debugInfo, null, 2)}</pre>
</div>
</details>
<div className="text-sm text-gray-600">
<p className="font-medium">Troubleshooting:</p>
<ul className="list-disc pl-5 space-y-1 mt-1">
<li>Verify <code>PROJECT_UUID</code> and <code>SERVER_UUID</code> are correct</li>
<li>Check if environment <code>{ENVIRONMENT_NAME}</code> exists</li>
<li>Ensure your Coolify version supports this API format</li>
</ul>
</div>
</div>
)}
</div>
);
}
// [{"uuid":"sgswssowscc84o8sc8wockgc","description":"This is the server where Coolify is running on. Don't delete this!","name":"localhost","ip":"host.docker.internal","is_coolify_host":true,"is_reachable":true,"is_usable":true,"port":22,"proxy":{"redirect_enabled":true},"settings":{"id":1,"concurrent_builds":2,"delete_unused_networks":false,"delete_unused_volumes":false,"docker_cleanup_frequency":"0 0 * * *","docker_cleanup_threshold":80,"dynamic_timeout":3600,"force_disabled":false,"force_docker_cleanup":true,"generate_exact_labels":false,"is_build_server":false,"is_cloudflare_tunnel":false,"is_jump_server":false,"is_logdrain_axiom_enabled":false,"is_logdrain_custom_enabled":false,"is_logdrain_highlight_enabled":false,"is_logdrain_newrelic_enabled":false,"is_metrics_enabled":false,"is_reachable":true,"is_sentinel_debug_enabled":false,"is_sentinel_enabled":false,"is_swarm_manager":false,"is_swarm_worker":false,"is_usable":true,"logdrain_axiom_api_key":null,"logdrain_axiom_dataset_name":null,"logdrain_custom_config":null,"logdrain_custom_config_parser":null,"logdrain_highlight_project_id":null,"logdrain_newrelic_base_uri":null,"logdrain_newrelic_license_key":null,"sentinel_custom_url":"http:\/\/host.docker.internal:8000","sentinel_metrics_history_days":7,"sentinel_metrics_refresh_rate_seconds":10,"sentinel_push_interval_seconds":60,"sentinel_token":"eyJpdiI6IllwMlBsOUtXODdUR0ZIbWtZenJRWFE9PSIsInZhbHVlIjoiZFUxcE9zSXFXdkVrN0tDUGdSbHpGVTE3cHVEeFlBM1hFWk56S05NVWVmVmxHV0tBQ2kra25uRnVzRzNHaFpBSGVTaGZLMGpqdS9nMU85MXhmS3VYMVE9PSIsIm1hYyI6ImY3ODQyNGI2OTVlMmRiMzFmNzJjZjRlYjNkNDIxOGVmZTAxOWMyNjY0ZTYxODE5MDIwY2FhMGUwYjU4ODMyN2MiLCJ0YWciOiIifQ==","server_disk_usage_check_frequency":"0 23 * * *","server_disk_usage_notification_threshold":80,"server_id":0,"server_timezone":"UTC","wildcard_domain":null,"created_at":"2025-04-03T17:12:45.000000Z","updated_at":"2025-04-03T17:16:25.000000Z"},"user":"root"}]

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
import Table from "./ui/table"; import Table from "./ui/table";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
@ -28,7 +27,7 @@ interface Message {
user_type: string; user_type: string;
} }
const API_URL = 'http://localhost:2058/host-api/app/v1/ticket/index.php'; const API_URL = 'http://192.168.1.197:2058/v1/ticket/index.php';
function Ticketing() { function Ticketing() {
const [tickets, setTickets] = useState<Ticket[]>([]); const [tickets, setTickets] = useState<Ticket[]>([]);

26
src/components/Topic.tsx Normal file
View File

@ -0,0 +1,26 @@
import React, {useEffect, useState} from "react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
export default function TopicCreation(){
const API_URL = 'http://192.168.1.197:2058/v1/users/index.php';
// useEffect(() => {
// fetch(`${API_URL}?query=get-topic`)
// .then(response => response.json())
// .then{(data: any) => {
// console.log(data);
// }}
// })
return(
<>
<section className="container mx-auto px-4">
<div className="grid grid-cols md:grid-cols-2 lg:grid-cols-3">
<Card>
<CardContent>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo, excepturi ipsum et animi debitis doloremque quam aliquid quaerat. Totam officiis iste laudantium amet corrupti, doloribus sunt minima dolor odit. Ipsum.
</CardContent>
</Card>
</div>
</section>
</>
)
}

View File

@ -8,6 +8,7 @@ import { Separator } from "./ui/separator";
import { Textarea } from "./ui/textarea"; import { Textarea } from "./ui/textarea";
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import UpdateAvatar from './UpdateAvatar'; import UpdateAvatar from './UpdateAvatar';
import {localizeTime} from "../lib/localizeTime";
interface SessionData { interface SessionData {
[key: string]: any; [key: string]: any;
@ -24,13 +25,12 @@ export default function ProfilePage() {
const [userData, setUserData] = useState<UserData | null>(null); const [userData, setUserData] = useState<UserData | null>(null);
const [invoiceList, setInvoiceList] = useState<any[]>([]); const [invoiceList, setInvoiceList] = useState<any[]>([]);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const USER_API_URL = 'http://192.168.1.197:2058/v1/users/index.php';
const INVOICE_API_URL = 'http://192.168.1.197:2058/v1/invoice/';
useEffect(() => { useEffect(() => {
const fetchSessionData = async () => { const fetchSessionData = async () => {
try { try {
const response = await fetch( const response = await fetch(`${USER_API_URL}?query=get-user`, {
'http://localhost:2058/host-api/v1/users/get-profile-data/',
{
credentials: 'include', // Crucial for cookies credentials: 'include', // Crucial for cookies
headers: { 'Accept': 'application/json' } headers: { 'Accept': 'application/json' }
} }
@ -49,7 +49,7 @@ export default function ProfilePage() {
}; };
const getInvoiceListData = async () => { const getInvoiceListData = async () => {
try { try {
const response = await fetch('http://localhost:2058/host-api/v1/invoice/invoice-info/', { const response = await fetch(`${USER_API_URL}?query=invoice-info`, {
method: 'GET', method: 'GET',
credentials: 'include', // Crucial for cookies credentials: 'include', // Crucial for cookies
headers: { 'Accept': 'application/json' } headers: { 'Accept': 'application/json' }
@ -105,7 +105,6 @@ export default function ProfilePage() {
<AvatarFallback>JP</AvatarFallback> <AvatarFallback>JP</AvatarFallback>
</Avatar> </Avatar>
<UpdateAvatar /> <UpdateAvatar />
</div> </div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
@ -128,17 +127,22 @@ export default function ProfilePage() {
</Card> </Card>
<Card className="mt-6"> <Card className="mt-6">
<CardHeader> <CardHeader>
<div className="flex flex-row justify-between">
<CardTitle>Billing Information</CardTitle> <CardTitle>Billing Information</CardTitle>
<a href="" className="hover:bg-[#6d9e37] hover:text-white transtion duration-500 py-1 px-2 rounded">View All</a>
</div>
<CardDescription> <CardDescription>
View your billing history. View your billing history.
</CardDescription> </CardDescription>
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr> <tr>
<th className="text-left">Invoice ID</th> <th className="text-left">Invoice</th>
<th className="text-left">Date</th> <th className="text-left">Invoice Date</th>
<th className="text-left">Description</th> <th className="text-left">Description</th>
<th className="text-right">Amount</th> <th className="text-right">Amount</th>
<th className="text-right">Status</th>
<th className="text-center">Action</th> <th className="text-center">Action</th>
</tr> </tr>
</thead> </thead>
@ -146,11 +150,14 @@ export default function ProfilePage() {
{ {
invoiceList.map((invoice) => ( invoiceList.map((invoice) => (
<tr key={invoice.id}> <tr key={invoice.id}>
<td>{invoice.invoice_id}</td> <td>{invoice.invoice_number}</td>
<td>{invoice.date}</td> <td>{invoice.invoice_date}</td>
<td>{invoice.description}</td> <td>{invoice.notes ? invoice.notes : ''}</td>
<td className="text-right">{invoice.amount}</td> <td className="text-right">{invoice.total_amount}</td>
<td className="text-center"><a href="">Print</a></td> <td className="text-right">{invoice.status}</td>
<td className="text-center">
<a href="">View</a>
</td>
</tr> </tr>
)) ))
} }

View File

@ -0,0 +1,9 @@
---
import Layout from "../layouts/Layout.astro";
import MakePaymentPage from "../components/MakePayment";
---
<Layout title="">
<div>
<MakePaymentPage client:load />
</div>
</Layout>

9
src/pages/payment.astro Normal file
View File

@ -0,0 +1,9 @@
---
import Layout from "../layouts/Layout.astro";
---
<Layout title="">
<div>
<h1>Payment Page</h1>
<h2>We are working on this!</h2>
</div>
</Layout>

View File

@ -0,0 +1,7 @@
---
import Layout from "../layouts/Layout.astro";
import TestCools from "../components/TestCool";
---
<Layout title="">
<TestCools client:load />
</Layout>

7
src/pages/topic.astro Normal file
View File

@ -0,0 +1,7 @@
---
import Layout from "../layouts/Layout.astro";
import TopicList from "../components/Topic";
---
<Layout title="">
<TopicList client:load />
</Layout>

View File

@ -1919,7 +1919,7 @@ jiti@^1.21.6, jiti@>=1.21.0:
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz" resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz"
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@ -1983,6 +1983,13 @@ longest-streak@^3.0.0:
resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz" resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz"
integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^10.2.0, lru-cache@^10.4.3: lru-cache@^10.2.0, lru-cache@^10.4.3:
version "10.4.3" version "10.4.3"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
@ -2563,7 +2570,7 @@ npm-run-path@^5.1.0:
dependencies: dependencies:
path-key "^4.0.0" path-key "^4.0.0"
object-assign@^4.0.1: object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@ -2798,6 +2805,15 @@ prompts@^2.4.2:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
property-information@^6.0.0: property-information@^6.0.0:
version "6.5.0" version "6.5.0"
resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz" resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz"
@ -2808,6 +2824,11 @@ property-information@^7.0.0:
resolved "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz" resolved "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz"
integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==
qr.js@0.0.0:
version "0.0.0"
resolved "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz"
integrity sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==
queue-microtask@^1.2.2: queue-microtask@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
@ -2825,6 +2846,19 @@ radix3@^1.1.2:
dependencies: dependencies:
scheduler "^0.25.0" scheduler "^0.25.0"
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-qr-code@^2.0.15:
version "2.0.15"
resolved "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.15.tgz"
integrity sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==
dependencies:
prop-types "^15.8.1"
qr.js "0.0.0"
react-refresh@^0.14.2: react-refresh@^0.14.2:
version "0.14.2" version "0.14.2"
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz" resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz"
@ -2879,7 +2913,7 @@ react-to-print@^3.0.5:
resolved "https://registry.npmjs.org/react-to-print/-/react-to-print-3.0.5.tgz" resolved "https://registry.npmjs.org/react-to-print/-/react-to-print-3.0.5.tgz"
integrity sha512-Z15MwMOzYCHWi26CZeFNwflAg7Nr8uWD6FTj+EkfIOjYyjr0MXGbI0c7rF4Fgrbj3XG9hFndb1ourxpPz2RAiA== integrity sha512-Z15MwMOzYCHWi26CZeFNwflAg7Nr8uWD6FTj+EkfIOjYyjr0MXGbI0c7rF4Fgrbj3XG9hFndb1ourxpPz2RAiA==
"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ~19", "react@^17.0.2 || ^18.0.0 || ^19.0.0", react@^19.0.0, react@>=16.8.0, react@>=18: react@*, "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ~19", "react@^17.0.2 || ^18.0.0 || ^19.0.0", react@^19.0.0, react@>=16.8.0, react@>=18:
version "19.0.0" version "19.0.0"
resolved "https://registry.npmjs.org/react/-/react-19.0.0.tgz" resolved "https://registry.npmjs.org/react/-/react-19.0.0.tgz"
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==