Compare commits
3 Commits
tpm1
...
get-starte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b58068d108 | ||
|
|
f9d1556ce9 | ||
| 0900d51954 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,7 +5,7 @@ dist/
|
|||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
public/host-api/
|
|
||||||
# logs
|
# logs
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -1,14 +0,0 @@
|
|||||||
### git flow guide / protocol
|
|
||||||
|
|
||||||
# dev - any one is free to create / push to / any experiment on this branch
|
|
||||||
# staging - it is the stage for all developer. all branchout and push over here.
|
|
||||||
*start point / fork point is staging, i.e. any fix/feature/improvement - you must start branching out from staging.
|
|
||||||
|
|
||||||
*better to name your branch with 'z-' , i.e. z-layout-footer-improvement (z-: identifier for temporary and safe to remove from the central repo -> then the file identifier -> then short info regarding the intent)
|
|
||||||
*many developers prefer feat/feature_name, i faced issue managing those branch using cli (bsd even alpine) for the slash(/)
|
|
||||||
## staging must have a automated test and deploy mechanism, so that developers can can always be on the same page regarding build/merge conflict issue
|
|
||||||
# test - staging to test by tester
|
|
||||||
# master - test to master by tester after full test
|
|
||||||
# release - master to release with version tag
|
|
||||||
|
|
||||||
final packaging and release
|
|
||||||
@@ -6,8 +6,7 @@
|
|||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro"
|
||||||
"push-s33": "rsync -rv --exclude .hta_config/conf.php dist/ dev2@siliconpin.s33.siliconpin.com:/home/dev2/domains/siliconpin.s33.siliconpin.com/public_html/"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/react": "^4.2.1",
|
"@astrojs/react": "^4.2.1",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 191 KiB |
@@ -1,48 +0,0 @@
|
|||||||
TLDR;
|
|
||||||
=====================================================
|
|
||||||
Nirvana License, i.e. Beings needs no license
|
|
||||||
=====================================================
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
------------
|
|
||||||
GNU, MIT, APACHE, Opensource, Source available -
|
|
||||||
So many options, why a new one?
|
|
||||||
To add / remove confusion may be, or
|
|
||||||
Just to remind Humans, you have evolved to as
|
|
||||||
being, you don't need one. Having license is like
|
|
||||||
having limitation, boundation not freedom, You
|
|
||||||
can have a Driving license and drive irresponsibly,
|
|
||||||
license provider / authority can impose some
|
|
||||||
nonsense rules. No matter how hard we try, Beings
|
|
||||||
don't like to be bound, limited...
|
|
||||||
They want freedom... little more - Nirvana.
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
Experience
|
|
||||||
------------
|
|
||||||
Best of the best GPL(copyleft) and it's derivatives
|
|
||||||
are confusing which one to choose & its protected
|
|
||||||
by copyright laws, but i get the idea of 4 freedoms.
|
|
||||||
Developers don't want to deal with lawyers (seems
|
|
||||||
wasting years), we want to build something.
|
|
||||||
I have seen license violation a lot, as they have
|
|
||||||
a fleet of lawyers.
|
|
||||||
I have seen Open Source is being used a suppressor
|
|
||||||
to GNU,GPL -Freedom centric ones. Going against the
|
|
||||||
ideology of creator of the open source definition
|
|
||||||
Bruce Perens, Eric S. Raymond,
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Quote
|
|
||||||
--------
|
|
||||||
Every rule / law has an exception except this.
|
|
||||||
Beings do not need a license, enough to have sense.
|
|
||||||
- Suvankar Sarkar (AKA Kar)
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
=====================================================
|
|
||||||
Mentioned some licenses above good to bad, there may be
|
|
||||||
some reason to use any existing license.
|
|
||||||
you are free to choose this one, defined below.
|
|
||||||
.....................................................
|
|
||||||
@@ -30,8 +30,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
const [dnsVerified, setDnsVerified] = useState({
|
const [dnsVerified, setDnsVerified] = useState({
|
||||||
cname: false,
|
cname: false,
|
||||||
ns: false,
|
ns: false,
|
||||||
a: false,
|
a: false
|
||||||
ip: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Form validation
|
// Form validation
|
||||||
@@ -62,7 +61,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validateForm();
|
validateForm();
|
||||||
}, [useCustomDomain, dnsVerified.cname, dnsVerified.ns, dnsVerified.ns, domainType, dnsMethod]);
|
}, [useCustomDomain, dnsVerified.cname, dnsVerified.ns, domainType, dnsMethod]);
|
||||||
|
|
||||||
// Show toast notification
|
// Show toast notification
|
||||||
const showToast = (message) => {
|
const showToast = (message) => {
|
||||||
@@ -111,8 +110,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
setDnsVerified({
|
setDnsVerified({
|
||||||
cname: false,
|
cname: false,
|
||||||
ns: false,
|
ns: false,
|
||||||
a: false,
|
a: false
|
||||||
ip: false
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Force SiliconPin subdomain to be checked if custom domain is checked
|
// Force SiliconPin subdomain to be checked if custom domain is checked
|
||||||
@@ -189,23 +187,45 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
setValidationMessage('');
|
setValidationMessage('');
|
||||||
setShowDnsConfig(false);
|
setShowDnsConfig(false);
|
||||||
|
|
||||||
// Simulate an API call to validate the domain
|
fetch('/validate-domain', {
|
||||||
setTimeout(() => {
|
method: 'POST',
|
||||||
// Simulate a real domain check - in a real app this would be an API call
|
headers: {
|
||||||
const checkResult = true; // Assume domain is valid for demo
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
domain,
|
||||||
|
type: domainType
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
const checkResult = data.status === "success";
|
||||||
|
console.log("finding:: checkResult:: ", checkResult, data);
|
||||||
|
|
||||||
setIsValidating(false);
|
setIsValidating(false);
|
||||||
setIsValidDomain(checkResult);
|
setIsValidDomain(checkResult);
|
||||||
|
|
||||||
if (checkResult) {
|
if (checkResult) {
|
||||||
setValidationMessage('Domain is valid and registered.');
|
setValidationMessage('Domain is valid and registered.');
|
||||||
setShowDnsConfig(true);
|
setShowDnsConfig(true);
|
||||||
} else {
|
} else {
|
||||||
setValidationMessage('Domain appears to be unregistered or unavailable.');
|
setValidationMessage('Domain appears to be unregistered or unavailable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
validateForm();
|
validateForm();
|
||||||
}, 1500);
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error validating domain:', error);
|
||||||
|
setIsValidating(false);
|
||||||
|
setIsValidDomain(false);
|
||||||
|
setValidationMessage('Error checking domain. Please try again.');
|
||||||
|
validateForm();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check DNS configuration
|
// Check DNS configuration
|
||||||
@@ -248,10 +268,6 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
setFormValid(false);
|
setFormValid(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dnsMethod === 'ip' && !dnsVerified.ip) {
|
|
||||||
setFormValid(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setFormValid(true);
|
setFormValid(true);
|
||||||
@@ -267,7 +283,20 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In a real app, this would submit the form data to the server
|
// In a real app, this would submit the form data to the server
|
||||||
console.log([{ deploymentType, appType, sampleWebAppType, sourceType, repoUrl, deploymentKey, useSubdomain, useCustomDomain, customDomain, customSubdomain, domainType, dnsMethod}]);
|
console.log({
|
||||||
|
deploymentType,
|
||||||
|
appType,
|
||||||
|
sampleWebAppType,
|
||||||
|
sourceType,
|
||||||
|
repoUrl,
|
||||||
|
deploymentKey,
|
||||||
|
useSubdomain,
|
||||||
|
useCustomDomain,
|
||||||
|
customDomain,
|
||||||
|
customSubdomain,
|
||||||
|
domainType,
|
||||||
|
dnsMethod
|
||||||
|
});
|
||||||
|
|
||||||
showToast('Form submitted successfully!');
|
showToast('Form submitted successfully!');
|
||||||
};
|
};
|
||||||
@@ -543,10 +572,9 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
|
|
||||||
{/* Domain Validation */}
|
{/* Domain Validation */}
|
||||||
<button
|
<button
|
||||||
disabled={!customDomain}
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={validateDomain}
|
onClick={validateDomain}
|
||||||
className={`px-4 py-2 ${!customDomain ? 'bg-neutral-600 cursor-not-allowed' : 'bg-[#6d9e37] focus:ring-[#6d9e37] transition-colors'} text-white font-medium rounded-md transition-colors focus:outline-none`}
|
className="px-4 py-2 bg-neutral-600 text-white font-medium rounded-md hover:bg-neutral-500 transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-500"
|
||||||
>
|
>
|
||||||
Validate Domain
|
Validate Domain
|
||||||
</button>
|
</button>
|
||||||
@@ -576,7 +604,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
|
|
||||||
{/* 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 border border-neutral-600 space-y-3">
|
||||||
<label for="dns-cname" className="flex items-start cursor-pointer">
|
<div className="flex items-start">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id="dns-cname"
|
id="dns-cname"
|
||||||
@@ -616,114 +644,67 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 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 border border-neutral-600 space-y-3">
|
<div className="flex items-start">
|
||||||
<label for="dns-ns" className="flex items-start cursor-pointer">
|
<input
|
||||||
<input
|
type="radio"
|
||||||
type="radio"
|
id="dns-ns"
|
||||||
id="dns-ns"
|
name="dns-method"
|
||||||
name="dns-method"
|
value="ns"
|
||||||
value="ns"
|
checked={dnsMethod === 'ns'}
|
||||||
checked={dnsMethod === 'ns'}
|
onChange={handleDnsMethodChange}
|
||||||
onChange={handleDnsMethodChange}
|
className="mt-1 mr-2"
|
||||||
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>
|
|
||||||
|
|
||||||
<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
|
|
||||||
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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns2.siliconpin.com</div>
|
|
||||||
<button
|
|
||||||
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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-2 text-right">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => checkDnsConfig('ns')}
|
onClick={() => copyToClipboard('ns1.siliconpin.com')}
|
||||||
className={`px-3 py-1 text-white text-sm rounded
|
className="ml-2 text-[#6d9e37] hover:text-white"
|
||||||
${dnsVerified.ns
|
aria-label="Copy nameserver value"
|
||||||
? 'bg-green-700 hover:bg-green-600'
|
|
||||||
: 'bg-neutral-600 hover:bg-neutral-500'}`}
|
|
||||||
>
|
>
|
||||||
{dnsVerified.ns ? '✓ Nameservers Verified' : 'Check Nameservers'}
|
<span className="text-lg">📋</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns2.siliconpin.com</div>
|
||||||
|
<button
|
||||||
|
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>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3">
|
<div className="mt-2 text-right">
|
||||||
<label for="dns-ip" className="flex items-start cursor-pointer">
|
<button
|
||||||
<input
|
type="button"
|
||||||
type="radio"
|
onClick={() => checkDnsConfig('ns')}
|
||||||
id="dns-ip"
|
className={`px-3 py-1 text-white text-sm rounded
|
||||||
name="dns-method"
|
${dnsVerified.ns
|
||||||
value="ip"
|
? 'bg-green-700 hover:bg-green-600'
|
||||||
checked={dnsMethod === 'ip'}
|
: 'bg-neutral-600 hover:bg-neutral-500'}`}
|
||||||
onChange={handleDnsMethodChange}
|
>
|
||||||
className="mt-1 mr-2"
|
{dnsVerified.ns ? '✓ Nameservers Verified' : 'Check Nameservers'}
|
||||||
/>
|
</button>
|
||||||
<div className="flex-1">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="mt-3 space-y-2">
|
|
||||||
<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>
|
|
||||||
<button
|
|
||||||
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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 text-right">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => checkDnsConfig('ip')}
|
|
||||||
className={`px-3 py-1 text-white text-sm rounded
|
|
||||||
${dnsMethod === 'ip'
|
|
||||||
? 'bg-green-700 hover:bg-green-600'
|
|
||||||
: 'bg-neutral-600 hover:bg-neutral-500'}`}
|
|
||||||
>
|
|
||||||
{/* {dnsVerified.ip ? '✓ IP Address Verified' : 'Check IP Address'} */}
|
|
||||||
Proceed to Pay
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -733,6 +714,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Form Submit Button */}
|
{/* Form Submit Button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -746,6 +728,7 @@ 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>
|
||||||
);
|
);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for app deployment options
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.appType - Selected app type
|
||||||
|
* @param {Function} props.onAppTypeChange - Handler for app type change
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const AppDeployment = ({ appType, onAppTypeChange }) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="app-type" className="block text-white font-medium">Select Application</label>
|
||||||
|
<select
|
||||||
|
id="app-type"
|
||||||
|
value={appType}
|
||||||
|
onChange={onAppTypeChange}
|
||||||
|
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="wordpress">🔌 WordPress</option>
|
||||||
|
<option value="prestashop">🛒 PrestaShop</option>
|
||||||
|
<option value="laravel">🚀 Laravel</option>
|
||||||
|
<option value="cakephp">🍰 CakePHP</option>
|
||||||
|
<option value="symfony">🎯 Symfony</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppDeployment;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for source code deployment options
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.sourceType - Source type (public/private)
|
||||||
|
* @param {string} props.repoUrl - Repository URL
|
||||||
|
* @param {string} props.deploymentKey - Deployment key for private repos
|
||||||
|
* @param {Function} props.onSourceTypeChange - Handler for source type change
|
||||||
|
* @param {Function} props.onRepoUrlChange - Handler for repo URL change
|
||||||
|
* @param {Function} props.onDeploymentKeyChange - Handler for deployment key change
|
||||||
|
* @param {Function} props.showToast - Function to show toast notifications
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const SourceDeployment = ({
|
||||||
|
sourceType,
|
||||||
|
repoUrl,
|
||||||
|
deploymentKey,
|
||||||
|
onSourceTypeChange,
|
||||||
|
onRepoUrlChange,
|
||||||
|
onDeploymentKeyChange,
|
||||||
|
showToast
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="source-type" className="block text-white font-medium">Source Type</label>
|
||||||
|
<select
|
||||||
|
id="source-type"
|
||||||
|
value={sourceType}
|
||||||
|
onChange={onSourceTypeChange}
|
||||||
|
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="public">🌐 Public Repository</option>
|
||||||
|
<option value="private">🔒 Private Repository</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<label htmlFor="repo-url" className="block text-white font-medium mb-2">Repository URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="repo-url"
|
||||||
|
value={repoUrl}
|
||||||
|
onChange={onRepoUrlChange}
|
||||||
|
placeholder="https://github.com/username/repository"
|
||||||
|
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]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{sourceType === 'private' && (
|
||||||
|
<div className="pt-2">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<label htmlFor="deployment-key" className="block text-white font-medium">Deployment Key</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => showToast('Deployment keys are used for secure access to private repositories')}
|
||||||
|
className="text-neutral-400 hover:text-white focus:outline-none"
|
||||||
|
aria-label="Deployment Key Information"
|
||||||
|
>
|
||||||
|
<span className="text-lg">❓</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
id="deployment-key"
|
||||||
|
value={deploymentKey}
|
||||||
|
onChange={onDeploymentKeyChange}
|
||||||
|
placeholder="Paste your SSH private key here"
|
||||||
|
rows="6"
|
||||||
|
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] font-mono text-sm"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-sm text-neutral-400">Your private key is used only for deploying and is never stored on our servers.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SourceDeployment;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for static site deployment options
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.fileName - Selected file name
|
||||||
|
* @param {Function} props.onFileChange - Handler for file change
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const StaticDeployment = ({ fileName, onFileChange }) => {
|
||||||
|
const fileInputRef = useRef(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="p-4 bg-neutral-700/50 rounded-md text-neutral-300 text-center">
|
||||||
|
Upload the zip file containing your static website
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<label htmlFor="file-upload" className="block text-white font-medium mb-2">Upload File (ZIP/TAR)</label>
|
||||||
|
<div className="flex items-center justify-center w-full">
|
||||||
|
<label
|
||||||
|
htmlFor="file-upload"
|
||||||
|
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer border-neutral-600 hover:border-[#6d9e37]"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
|
<span className="text-3xl mb-3 text-neutral-400">📁</span>
|
||||||
|
<p className="mb-2 text-sm text-neutral-400">
|
||||||
|
<span className="font-semibold">Click to upload</span> or drag and drop
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-neutral-500">ZIP or TAR files only (max. 100MB)</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="file-upload"
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
onChange={onFileChange}
|
||||||
|
className="hidden"
|
||||||
|
accept=".zip,.tar"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{fileName && (
|
||||||
|
<div className="mt-2 text-sm text-neutral-400">
|
||||||
|
Selected file: {fileName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StaticDeployment;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for sample web app deployment options
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.templateType - Selected template type
|
||||||
|
* @param {Function} props.onTemplateTypeChange - Handler for template type change
|
||||||
|
* @param {React.Component} props.TemplatePreview - Template preview component
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const TemplateDeployment = ({ templateType, onTemplateTypeChange, TemplatePreview }) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="sample-web-app-type" className="block text-white font-medium">Select Template Type</label>
|
||||||
|
<select
|
||||||
|
id="sample-web-app-type"
|
||||||
|
value={templateType}
|
||||||
|
onChange={onTemplateTypeChange}
|
||||||
|
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="developer">👨💻 Developer Portfolio</option>
|
||||||
|
<option value="designer">🎨 Designer Portfolio</option>
|
||||||
|
<option value="photographer">📸 Photographer Portfolio</option>
|
||||||
|
<option value="documentation">📚 Documentation Site</option>
|
||||||
|
<option value="business">🏢 Single Page Business Site</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{/* Information box */}
|
||||||
|
<div className="mt-4 p-4 bg-neutral-700/30 rounded-md border border-neutral-600">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="mr-3 text-2xl">ℹ️</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm text-neutral-300">
|
||||||
|
Deploy a ready-to-use template that you can customize. We'll set up the basic structure and you can modify it to fit your needs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Template Preview */}
|
||||||
|
<TemplatePreview templateType={templateType} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TemplateDeployment;
|
||||||
98
src/components/DomainSetupForm/DeploymentOptions/index.jsx
Normal file
98
src/components/DomainSetupForm/DeploymentOptions/index.jsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import AppDeployment from './AppDeployment';
|
||||||
|
import SourceDeployment from './SourceDeployment';
|
||||||
|
import StaticDeployment from './StaticDeployment';
|
||||||
|
import TemplateDeployment from './TemplateDeployment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main component for deployment options
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {Object} props.deploymentConfig - Deployment configuration state
|
||||||
|
* @param {Object} props.handlers - Event handlers for deployment options
|
||||||
|
* @param {Function} props.showToast - Function to show toast notifications
|
||||||
|
* @param {React.Component} props.TemplatePreview - Template preview component
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const DeploymentOptions = ({
|
||||||
|
deploymentConfig,
|
||||||
|
handlers,
|
||||||
|
showToast,
|
||||||
|
TemplatePreview
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
appType,
|
||||||
|
sampleWebAppType,
|
||||||
|
sourceType,
|
||||||
|
repoUrl,
|
||||||
|
deploymentKey,
|
||||||
|
fileName
|
||||||
|
} = deploymentConfig;
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleDeploymentTypeChange,
|
||||||
|
handleAppTypeChange,
|
||||||
|
handleSampleWebAppTypeChange,
|
||||||
|
handleSourceTypeChange,
|
||||||
|
handleRepoUrlChange,
|
||||||
|
handleDeploymentKeyChange,
|
||||||
|
handleFileChange
|
||||||
|
} = handlers;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Deployment Type Selection */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="deployment-type" className="block text-white font-medium">Deployment Type</label>
|
||||||
|
<select
|
||||||
|
id="deployment-type"
|
||||||
|
value={type}
|
||||||
|
onChange={handleDeploymentTypeChange}
|
||||||
|
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="app">💻 Deploy an App</option>
|
||||||
|
<option value="source">⚙️ From Source</option>
|
||||||
|
<option value="static">📄 Static Site Upload</option>
|
||||||
|
<option value="sample-web-app">🌐 Sample Web App</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Render different components based on deployment type */}
|
||||||
|
{type === 'app' && (
|
||||||
|
<AppDeployment
|
||||||
|
appType={appType}
|
||||||
|
onAppTypeChange={handleAppTypeChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{type === 'sample-web-app' && (
|
||||||
|
<TemplateDeployment
|
||||||
|
templateType={sampleWebAppType}
|
||||||
|
onTemplateTypeChange={handleSampleWebAppTypeChange}
|
||||||
|
TemplatePreview={TemplatePreview}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{type === 'source' && (
|
||||||
|
<SourceDeployment
|
||||||
|
sourceType={sourceType}
|
||||||
|
repoUrl={repoUrl}
|
||||||
|
deploymentKey={deploymentKey}
|
||||||
|
onSourceTypeChange={handleSourceTypeChange}
|
||||||
|
onRepoUrlChange={handleRepoUrlChange}
|
||||||
|
onDeploymentKeyChange={handleDeploymentKeyChange}
|
||||||
|
showToast={showToast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{type === 'static' && (
|
||||||
|
<StaticDeployment
|
||||||
|
fileName={fileName}
|
||||||
|
onFileChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeploymentOptions;
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import DnsConfiguration from './DnsConfiguration';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for custom domain configuration
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {Object} props.domainConfig - Domain configuration state
|
||||||
|
* @param {Object} props.validation - Domain validation state
|
||||||
|
* @param {Object} props.dnsVerified - DNS verification state
|
||||||
|
* @param {Object} props.handlers - Event handlers for domain config
|
||||||
|
* @param {Function} props.checkDnsConfig - Function to check DNS configuration
|
||||||
|
* @param {string} props.defaultSubdomain - Default SiliconPin subdomain
|
||||||
|
* @param {Function} props.showToast - Function to show toast notifications
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const CustomDomain = ({
|
||||||
|
domainConfig,
|
||||||
|
validation,
|
||||||
|
dnsVerified,
|
||||||
|
handlers,
|
||||||
|
checkDnsConfig,
|
||||||
|
defaultSubdomain,
|
||||||
|
showToast
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
domainType,
|
||||||
|
customDomain,
|
||||||
|
customSubdomain,
|
||||||
|
dnsMethod
|
||||||
|
} = domainConfig;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isValidating,
|
||||||
|
isValidDomain,
|
||||||
|
validationMessage,
|
||||||
|
showDnsConfig
|
||||||
|
} = validation;
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleDomainTypeChange,
|
||||||
|
handleDomainChange,
|
||||||
|
handleSubdomainChange,
|
||||||
|
handleDnsMethodChange,
|
||||||
|
validateDomain
|
||||||
|
} = handlers;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-3 space-y-4">
|
||||||
|
{/* 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 items-center">
|
||||||
|
<input
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Domain Input */}
|
||||||
|
{domainType === 'domain' ? (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="custom-domain"
|
||||||
|
value={customDomain}
|
||||||
|
onChange={handleDomainChange}
|
||||||
|
placeholder="yourdomain.com"
|
||||||
|
aria-label="Enter root domain"
|
||||||
|
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]"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-neutral-400">
|
||||||
|
Enter domain without http://, www, or trailing slashes (example.com). You can configure www or other subdomains later.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="custom-subdomain"
|
||||||
|
value={customSubdomain}
|
||||||
|
onChange={handleSubdomainChange}
|
||||||
|
placeholder="blog.yourdomain.com"
|
||||||
|
aria-label="Enter subdomain"
|
||||||
|
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]"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-neutral-400">
|
||||||
|
Enter the full subdomain without http:// or trailing slashes. www and protocol prefixes will be automatically removed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Domain Validation */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={validateDomain}
|
||||||
|
disabled={isValidating}
|
||||||
|
className="px-4 py-2 bg-neutral-600 text-white font-medium rounded-md hover:bg-neutral-500 transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isValidating ? 'Validating...' : 'Validate Domain'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Validation Status */}
|
||||||
|
{isValidating && (
|
||||||
|
<div className="p-3 bg-neutral-700/50 rounded-md">
|
||||||
|
<div className="flex items-center justify-center space-x-2">
|
||||||
|
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
|
||||||
|
<p className="text-white">Verifying domain registration and availability...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isValidating && validationMessage && (
|
||||||
|
<div className={`p-3 rounded-md ${isValidDomain ? 'bg-green-900/20 border border-green-700/50' : 'bg-red-900/20 border border-red-700/50'}`}>
|
||||||
|
<p className={isValidDomain ? 'text-green-400' : 'text-red-400'}>
|
||||||
|
{validationMessage}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* DNS Configuration Options */}
|
||||||
|
{showDnsConfig && (
|
||||||
|
<DnsConfiguration
|
||||||
|
domainType={domainType}
|
||||||
|
dnsMethod={dnsMethod}
|
||||||
|
defaultSubdomain={defaultSubdomain}
|
||||||
|
dnsVerified={dnsVerified}
|
||||||
|
onDnsMethodChange={handleDnsMethodChange}
|
||||||
|
checkDnsConfig={checkDnsConfig}
|
||||||
|
showToast={showToast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomDomain;
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { copyToClipboard } from '../../../utils/domainUtils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for DNS configuration options
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.domainType - Domain type ('domain' or 'subdomain')
|
||||||
|
* @param {string} props.dnsMethod - DNS method ('cname' or 'ns')
|
||||||
|
* @param {string} props.defaultSubdomain - Default SiliconPin subdomain
|
||||||
|
* @param {Object} props.dnsVerified - DNS verification state
|
||||||
|
* @param {Function} props.onDnsMethodChange - Handler for DNS method change
|
||||||
|
* @param {Function} props.checkDnsConfig - Function to check DNS configuration
|
||||||
|
* @param {Function} props.showToast - Function to show toast notifications
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const DnsConfiguration = ({
|
||||||
|
domainType,
|
||||||
|
dnsMethod,
|
||||||
|
defaultSubdomain,
|
||||||
|
dnsVerified,
|
||||||
|
onDnsMethodChange,
|
||||||
|
checkDnsConfig,
|
||||||
|
showToast
|
||||||
|
}) => {
|
||||||
|
// Handle copy to clipboard with toast feedback
|
||||||
|
const handleCopyToClipboard = async (text) => {
|
||||||
|
const result = await copyToClipboard(text);
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Copied to clipboard!');
|
||||||
|
} else {
|
||||||
|
showToast(`Failed to copy: ${result.error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 space-y-4 p-4 rounded-md bg-neutral-800/50 border border-neutral-700">
|
||||||
|
<h4 className="text-white font-medium">Connect Your Domain</h4>
|
||||||
|
|
||||||
|
{/* CNAME Record Option */}
|
||||||
|
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="dns-cname"
|
||||||
|
name="dns-method"
|
||||||
|
value="cname"
|
||||||
|
checked={dnsMethod === 'cname'}
|
||||||
|
onChange={onDnsMethodChange}
|
||||||
|
className="mt-1 mr-2"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div className="mt-3 flex items-center">
|
||||||
|
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">
|
||||||
|
{defaultSubdomain}.subdomain.siliconpin.com
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopyToClipboard(`${defaultSubdomain}.subdomain.siliconpin.com`)}
|
||||||
|
className="ml-2 text-[#6d9e37] hover:text-white"
|
||||||
|
aria-label="Copy CNAME value"
|
||||||
|
>
|
||||||
|
<span className="text-lg">📋</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-right">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => checkDnsConfig('cname')}
|
||||||
|
disabled={dnsVerified.cname === 'checking'}
|
||||||
|
className={`px-3 py-1 text-white text-sm rounded
|
||||||
|
${dnsVerified.cname === true
|
||||||
|
? 'bg-green-700 hover:bg-green-600'
|
||||||
|
: dnsVerified.cname === 'checking'
|
||||||
|
? 'bg-neutral-500 cursor-not-allowed'
|
||||||
|
: 'bg-neutral-600 hover:bg-neutral-500'}`}
|
||||||
|
>
|
||||||
|
{dnsVerified.cname === true
|
||||||
|
? '✓ CNAME Verified'
|
||||||
|
: dnsVerified.cname === 'checking'
|
||||||
|
? 'Checking...'
|
||||||
|
: 'Check CNAME'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nameserver Option (only for full domains, not subdomains) */}
|
||||||
|
{domainType === 'domain' && (
|
||||||
|
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="dns-ns"
|
||||||
|
name="dns-method"
|
||||||
|
value="ns"
|
||||||
|
checked={dnsMethod === 'ns'}
|
||||||
|
onChange={onDnsMethodChange}
|
||||||
|
className="mt-1 mr-2"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div className="mt-3 space-y-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns1.siliconpin.com</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopyToClipboard('ns1.siliconpin.com')}
|
||||||
|
className="ml-2 text-[#6d9e37] hover:text-white"
|
||||||
|
aria-label="Copy nameserver value"
|
||||||
|
>
|
||||||
|
<span className="text-lg">📋</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="bg-neutral-800 p-2 rounded font-mono text-sm text-neutral-300">ns2.siliconpin.com</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopyToClipboard('ns2.siliconpin.com')}
|
||||||
|
className="ml-2 text-[#6d9e37] hover:text-white"
|
||||||
|
aria-label="Copy nameserver value"
|
||||||
|
>
|
||||||
|
<span className="text-lg">📋</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-2 text-right">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => checkDnsConfig('ns')}
|
||||||
|
disabled={dnsVerified.ns === 'checking'}
|
||||||
|
className={`px-3 py-1 text-white text-sm rounded
|
||||||
|
${dnsVerified.ns === true
|
||||||
|
? 'bg-green-700 hover:bg-green-600'
|
||||||
|
: dnsVerified.ns === 'checking'
|
||||||
|
? 'bg-neutral-500 cursor-not-allowed'
|
||||||
|
: 'bg-neutral-600 hover:bg-neutral-500'}`}
|
||||||
|
>
|
||||||
|
{dnsVerified.ns === true
|
||||||
|
? '✓ Nameservers Verified'
|
||||||
|
: dnsVerified.ns === 'checking'
|
||||||
|
? 'Checking...'
|
||||||
|
: 'Check Nameservers'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DnsConfiguration;
|
||||||
91
src/components/DomainSetupForm/DomainConfiguration/index.jsx
Normal file
91
src/components/DomainSetupForm/DomainConfiguration/index.jsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import CustomDomain from './CustomDomain';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main component for domain configuration
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {Object} props.domainConfig - Domain configuration state
|
||||||
|
* @param {Object} props.validation - Domain validation state
|
||||||
|
* @param {Object} props.dnsVerified - DNS verification state
|
||||||
|
* @param {Object} props.handlers - Event handlers for domain config
|
||||||
|
* @param {Function} props.checkDnsConfig - Function to check DNS config
|
||||||
|
* @param {string} props.defaultSubdomain - Default SiliconPin subdomain
|
||||||
|
* @param {Function} props.showToast - Function to show toast notifications
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
const DomainConfiguration = ({
|
||||||
|
domainConfig,
|
||||||
|
validation,
|
||||||
|
dnsVerified,
|
||||||
|
handlers,
|
||||||
|
checkDnsConfig,
|
||||||
|
defaultSubdomain,
|
||||||
|
showToast
|
||||||
|
}) => {
|
||||||
|
const { useSubdomain, useCustomDomain } = domainConfig;
|
||||||
|
const {
|
||||||
|
handleUseSubdomainChange,
|
||||||
|
handleUseCustomDomainChange
|
||||||
|
} = handlers;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-4 border-t border-neutral-700">
|
||||||
|
<h3 className="text-lg font-medium text-white mb-4">Destination</h3>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* SiliconPin Subdomain */}
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="use-subdomain"
|
||||||
|
checked={useSubdomain}
|
||||||
|
onChange={handleUseSubdomainChange}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label htmlFor="use-subdomain" className="block text-white font-medium">Use SiliconPin Subdomain</label>
|
||||||
|
<div className="mt-2 flex">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="subdomain"
|
||||||
|
value={defaultSubdomain}
|
||||||
|
className="rounded-l-md py-2 px-3 bg-neutral-600 border-y border-l border-neutral-600 text-neutral-300 focus:outline-none w-1/3 font-mono"
|
||||||
|
readOnly
|
||||||
|
aria-label="Default subdomain"
|
||||||
|
/>
|
||||||
|
<span className="rounded-r-md py-2 px-3 bg-neutral-800 border border-neutral-700 text-neutral-400 w-2/3">.subdomain.siliconpin.com</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Custom Domain */}
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="use-custom-domain"
|
||||||
|
checked={useCustomDomain}
|
||||||
|
onChange={handleUseCustomDomainChange}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label htmlFor="use-custom-domain" className="block text-white font-medium">Use Custom Domain</label>
|
||||||
|
|
||||||
|
{useCustomDomain && (
|
||||||
|
<CustomDomain
|
||||||
|
domainConfig={domainConfig}
|
||||||
|
validation={validation}
|
||||||
|
dnsVerified={dnsVerified}
|
||||||
|
handlers={handlers}
|
||||||
|
checkDnsConfig={checkDnsConfig}
|
||||||
|
defaultSubdomain={defaultSubdomain}
|
||||||
|
showToast={showToast}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DomainConfiguration;
|
||||||
136
src/components/DomainSetupForm/index.jsx
Normal file
136
src/components/DomainSetupForm/index.jsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import DeploymentOptions from './DeploymentOptions';
|
||||||
|
import DomainConfiguration from './DomainConfiguration';
|
||||||
|
import { Toast } from '../shared/Toast';
|
||||||
|
import { TemplatePreview } from '../shared/TemplatePreview';
|
||||||
|
|
||||||
|
// Custom hooks
|
||||||
|
import useDeploymentConfig from '../../hooks/useDeploymentConfig';
|
||||||
|
import useDomainConfig from '../../hooks/useDomainConfig';
|
||||||
|
import useDnsVerification from '../../hooks/useDnsVerification';
|
||||||
|
import useToast from '../../hooks/useToast';
|
||||||
|
import useFormValidation from '../../hooks/useFormValidation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main DomainSetupForm component
|
||||||
|
* @param {Object} props - Component props
|
||||||
|
* @param {string} props.defaultSubdomain - Default SiliconPin subdomain
|
||||||
|
* @returns {JSX.Element} - Rendered component
|
||||||
|
*/
|
||||||
|
export const DomainSetupForm = ({ defaultSubdomain }) => {
|
||||||
|
// Initialize hooks for different concerns
|
||||||
|
const { toast, showToast } = useToast();
|
||||||
|
|
||||||
|
// Deployment configuration
|
||||||
|
const deploymentConfig = useDeploymentConfig();
|
||||||
|
|
||||||
|
// DNS verification (depends on domain config for reset logic)
|
||||||
|
const dnsVerificationHook = useDnsVerification(showToast);
|
||||||
|
const { dnsVerified, checkDnsConfig, resetAllDnsVerification } = dnsVerificationHook;
|
||||||
|
|
||||||
|
// Domain configuration (needs DNS reset function)
|
||||||
|
const domainConfig = useDomainConfig({}, resetAllDnsVerification);
|
||||||
|
|
||||||
|
// Pass the domain config to DNS verification hook for dependency tracking
|
||||||
|
// This is done after initialization to avoid circular dependencies
|
||||||
|
dnsVerificationHook.domainConfig = domainConfig.config;
|
||||||
|
|
||||||
|
// Form validation based on domain and DNS state
|
||||||
|
const { formValid } = useFormValidation(
|
||||||
|
domainConfig.config,
|
||||||
|
domainConfig.validation,
|
||||||
|
dnsVerified
|
||||||
|
);
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
const handleSubmit = useCallback((e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (domainConfig.useCustomDomain && !formValid) {
|
||||||
|
showToast('Please complete domain validation and DNS verification before deploying.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a real app, this would submit the form data to the server
|
||||||
|
console.log({
|
||||||
|
deploymentType: deploymentConfig.type,
|
||||||
|
appType: deploymentConfig.appType,
|
||||||
|
sampleWebAppType: deploymentConfig.sampleWebAppType,
|
||||||
|
sourceType: deploymentConfig.sourceType,
|
||||||
|
repoUrl: deploymentConfig.repoUrl,
|
||||||
|
deploymentKey: deploymentConfig.deploymentKey,
|
||||||
|
useSubdomain: domainConfig.useSubdomain,
|
||||||
|
useCustomDomain: domainConfig.useCustomDomain,
|
||||||
|
customDomain: domainConfig.customDomain,
|
||||||
|
customSubdomain: domainConfig.customSubdomain,
|
||||||
|
domainType: domainConfig.domainType,
|
||||||
|
dnsMethod: domainConfig.dnsMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
showToast('Form submitted successfully!');
|
||||||
|
}, [
|
||||||
|
formValid,
|
||||||
|
showToast,
|
||||||
|
deploymentConfig,
|
||||||
|
domainConfig
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-neutral-800 rounded-lg p-6 sm:p-8 border border-neutral-700">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
{/* Deployment Options Section */}
|
||||||
|
<DeploymentOptions
|
||||||
|
deploymentConfig={deploymentConfig}
|
||||||
|
handlers={{
|
||||||
|
handleDeploymentTypeChange: deploymentConfig.handleDeploymentTypeChange,
|
||||||
|
handleAppTypeChange: deploymentConfig.handleAppTypeChange,
|
||||||
|
handleSampleWebAppTypeChange: deploymentConfig.handleSampleWebAppTypeChange,
|
||||||
|
handleSourceTypeChange: deploymentConfig.handleSourceTypeChange,
|
||||||
|
handleRepoUrlChange: deploymentConfig.handleRepoUrlChange,
|
||||||
|
handleDeploymentKeyChange: deploymentConfig.handleDeploymentKeyChange,
|
||||||
|
handleFileChange: deploymentConfig.handleFileChange
|
||||||
|
}}
|
||||||
|
showToast={showToast}
|
||||||
|
TemplatePreview={TemplatePreview}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Domain Configuration Section */}
|
||||||
|
<DomainConfiguration
|
||||||
|
domainConfig={domainConfig.config}
|
||||||
|
validation={domainConfig.validation}
|
||||||
|
dnsVerified={dnsVerified}
|
||||||
|
handlers={{
|
||||||
|
handleUseSubdomainChange: domainConfig.handleUseSubdomainChange,
|
||||||
|
handleUseCustomDomainChange: domainConfig.handleUseCustomDomainChange,
|
||||||
|
handleDomainTypeChange: domainConfig.handleDomainTypeChange,
|
||||||
|
handleDnsMethodChange: domainConfig.handleDnsMethodChange,
|
||||||
|
handleDomainChange: domainConfig.handleDomainChange,
|
||||||
|
handleSubdomainChange: domainConfig.handleSubdomainChange,
|
||||||
|
validateDomain: domainConfig.validateDomain
|
||||||
|
}}
|
||||||
|
checkDnsConfig={checkDnsConfig}
|
||||||
|
defaultSubdomain={defaultSubdomain}
|
||||||
|
showToast={showToast}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Form Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={domainConfig.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
|
||||||
|
${domainConfig.useCustomDomain && !formValid
|
||||||
|
? 'bg-neutral-600 cursor-not-allowed'
|
||||||
|
: 'bg-[#6d9e37] hover:bg-[#598035] focus:ring-[#6d9e37] transition-colors'
|
||||||
|
}`}
|
||||||
|
aria-label="Start deployment"
|
||||||
|
>
|
||||||
|
Start the Deployment
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<Toast visible={toast.visible} message={toast.message} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DomainSetupForm;
|
||||||
22
src/components/atoms/SelectWithLabel/SelectWithLabel.tsx
Normal file
22
src/components/atoms/SelectWithLabel/SelectWithLabel.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const SelectWithLabel = ({value, onChange}: any)=>{
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<label htmlFor="deployment-type" className="block text-white font-medium">Deployment Type</label>
|
||||||
|
<select
|
||||||
|
id="deployment-type"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
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="app">💻 Deploy an App</option>
|
||||||
|
<option value="source">⚙️ From Source</option>
|
||||||
|
<option value="static">📄 Static Site Upload</option>
|
||||||
|
<option value="sample-web-app">🌐 Sample Web App</option>
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectWithLabel;
|
||||||
0
src/components/atoms/SelectWithLabel/index.tsx
Normal file
0
src/components/atoms/SelectWithLabel/index.tsx
Normal file
80
src/components/shared/TemplatePreview.jsx
Normal file
80
src/components/shared/TemplatePreview.jsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const TemplatePreview = ({ templateType }) => {
|
||||||
|
// Template information for different types
|
||||||
|
const templateInfo = {
|
||||||
|
developer: {
|
||||||
|
name: "Developer Portfolio",
|
||||||
|
description: "A modern, responsive portfolio site with sections for projects, skills, and contact information. Perfect for developers to showcase their work.",
|
||||||
|
features: ["Project showcase", "Skills section", "GitHub integration", "Contact form", "Blog ready"],
|
||||||
|
image: "https://images.unsplash.com/photo-1498050108023-c5249f4df085?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2072&q=80"
|
||||||
|
},
|
||||||
|
designer: {
|
||||||
|
name: "Designer Portfolio",
|
||||||
|
description: "A visually stunning portfolio for designers with image galleries, case studies, and animations to showcase creative work.",
|
||||||
|
features: ["Visual gallery", "Case studies", "Color scheme customization", "Smooth animations", "Design process showcase"],
|
||||||
|
image: "https://images.unsplash.com/photo-1561070791-2526d30994b5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2000&q=80"
|
||||||
|
},
|
||||||
|
photographer: {
|
||||||
|
name: "Photographer Portfolio",
|
||||||
|
description: "An elegant portfolio with fullscreen galleries, image zooming, and lightbox features designed for photographers to display their work.",
|
||||||
|
features: ["Fullscreen galleries", "Image zoom", "Lightbox", "Category filtering", "Client proofing"],
|
||||||
|
image: "https://images.unsplash.com/photo-1542038784456-1ea8e935640e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80"
|
||||||
|
},
|
||||||
|
documentation: {
|
||||||
|
name: "Documentation Site",
|
||||||
|
description: "A comprehensive documentation site with search, code snippets, and versioning support for technical documentation.",
|
||||||
|
features: ["Search functionality", "Code snippets with syntax highlighting", "Versioning", "Sidebar navigation", "Mobile-friendly"],
|
||||||
|
image: "https://images.unsplash.com/photo-1456406644174-8ddd4cd52a06?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2068&q=80"
|
||||||
|
},
|
||||||
|
business: {
|
||||||
|
name: "Single Page Business Site",
|
||||||
|
description: "A professional one-page website for businesses with sections for services, testimonials, team members, and contact information.",
|
||||||
|
features: ["Single page layout", "Services section", "Testimonials", "Team profiles", "Contact form with map"],
|
||||||
|
image: "https://images.unsplash.com/photo-1560179707-f14e90ef3623?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2073&q=80"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const template = templateInfo[templateType] || templateInfo.developer;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-6 bg-neutral-800 rounded-lg border border-neutral-700 overflow-hidden">
|
||||||
|
{/* Template preview image */}
|
||||||
|
<div className="relative h-48 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={template.image}
|
||||||
|
alt={`${template.name} Preview`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent flex items-end">
|
||||||
|
<div className="p-4">
|
||||||
|
<h3 className="text-xl font-semibold text-white">{template.name}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Template details */}
|
||||||
|
<div className="p-5 space-y-4">
|
||||||
|
<p className="text-neutral-300">{template.description}</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-medium mb-2">Features:</h4>
|
||||||
|
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
|
{template.features.map((feature, index) => (
|
||||||
|
<li key={index} className="text-neutral-400 flex items-center">
|
||||||
|
<span className="mr-2 text-[#6d9e37]">✓</span>
|
||||||
|
{feature}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<button className="px-4 py-2 bg-[#6d9e37] text-white rounded-md hover:bg-[#598035] transition-colors w-full">
|
||||||
|
Select This Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
20
src/components/shared/Toast.jsx
Normal file
20
src/components/shared/Toast.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const Toast = ({ visible, message, type = 'success' }) => {
|
||||||
|
// Color scheme based on type
|
||||||
|
const bgColor = type === 'error' ? 'bg-red-900' : 'bg-green-900';
|
||||||
|
const icon = type === 'error' ? '✕' : '✓';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`fixed bottom-5 right-5 p-3 ${bgColor} text-white rounded-md shadow-lg transform transition-all duration-300 z-50 ${
|
||||||
|
visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10 pointer-events-none'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-lg">{icon}</span>
|
||||||
|
<span>{message}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
103
src/hooks/useDeploymentConfig.js
Normal file
103
src/hooks/useDeploymentConfig.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing deployment configuration state
|
||||||
|
* @param {Object} initialConfig - Initial deployment configuration
|
||||||
|
* @returns {Object} - Deployment configuration state and updater functions
|
||||||
|
*/
|
||||||
|
const useDeploymentConfig = (initialConfig = {}) => {
|
||||||
|
const [config, setConfig] = useState({
|
||||||
|
type: 'app',
|
||||||
|
appType: 'wordpress',
|
||||||
|
sampleWebAppType: 'developer',
|
||||||
|
sourceType: 'public',
|
||||||
|
repoUrl: '',
|
||||||
|
deploymentKey: '',
|
||||||
|
fileName: '',
|
||||||
|
...initialConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract values for easier access
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
appType,
|
||||||
|
sampleWebAppType,
|
||||||
|
sourceType,
|
||||||
|
repoUrl,
|
||||||
|
deploymentKey,
|
||||||
|
fileName
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
// Update a single field
|
||||||
|
const updateField = useCallback((field, value) => {
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handler for deployment type change
|
||||||
|
const handleDeploymentTypeChange = useCallback((e) => {
|
||||||
|
updateField('type', e.target.value);
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for app type change
|
||||||
|
const handleAppTypeChange = useCallback((e) => {
|
||||||
|
updateField('appType', e.target.value);
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for sample web app type change
|
||||||
|
const handleSampleWebAppTypeChange = useCallback((e) => {
|
||||||
|
updateField('sampleWebAppType', e.target.value);
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for source type change
|
||||||
|
const handleSourceTypeChange = useCallback((e) => {
|
||||||
|
updateField('sourceType', e.target.value);
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for repo URL change
|
||||||
|
const handleRepoUrlChange = useCallback((e) => {
|
||||||
|
updateField('repoUrl', e.target.value);
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for deployment key change
|
||||||
|
const handleDeploymentKeyChange = useCallback((e) => {
|
||||||
|
updateField('deploymentKey', e.target.value);
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for file change
|
||||||
|
const handleFileChange = useCallback((e) => {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
updateField('fileName', e.target.files[0].name);
|
||||||
|
} else {
|
||||||
|
updateField('fileName', '');
|
||||||
|
}
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State values
|
||||||
|
config,
|
||||||
|
type,
|
||||||
|
appType,
|
||||||
|
sampleWebAppType,
|
||||||
|
sourceType,
|
||||||
|
repoUrl,
|
||||||
|
deploymentKey,
|
||||||
|
fileName,
|
||||||
|
|
||||||
|
// Update functions
|
||||||
|
updateField,
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
handleDeploymentTypeChange,
|
||||||
|
handleAppTypeChange,
|
||||||
|
handleSampleWebAppTypeChange,
|
||||||
|
handleSourceTypeChange,
|
||||||
|
handleRepoUrlChange,
|
||||||
|
handleDeploymentKeyChange,
|
||||||
|
handleFileChange
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDeploymentConfig;
|
||||||
62
src/hooks/useDnsVerification.js
Normal file
62
src/hooks/useDnsVerification.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing DNS verification
|
||||||
|
* @param {Function} showToast - Function to display toast notifications
|
||||||
|
* @returns {Object} - DNS verification state and methods
|
||||||
|
*/
|
||||||
|
const useDnsVerification = (showToast) => {
|
||||||
|
// DNS verification state
|
||||||
|
const [dnsVerified, setDnsVerified] = useState({
|
||||||
|
cname: false,
|
||||||
|
ns: false,
|
||||||
|
a: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check DNS configuration
|
||||||
|
const checkDnsConfig = useCallback((type) => {
|
||||||
|
showToast(`Checking ${type}... (This would verify DNS in a real app)`);
|
||||||
|
|
||||||
|
// Set type to 'checking' state
|
||||||
|
setDnsVerified(prev => ({
|
||||||
|
...prev,
|
||||||
|
[type]: 'checking'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Simulate DNS check with a delay
|
||||||
|
setTimeout(() => {
|
||||||
|
setDnsVerified(prev => ({
|
||||||
|
...prev,
|
||||||
|
[type]: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
showToast(`${type} verified successfully!`);
|
||||||
|
}, 1500);
|
||||||
|
}, [showToast]);
|
||||||
|
|
||||||
|
// Reset DNS verification for a specific type
|
||||||
|
const resetDnsVerification = useCallback((type) => {
|
||||||
|
setDnsVerified(prev => ({
|
||||||
|
...prev,
|
||||||
|
[type]: false
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset all DNS verification
|
||||||
|
const resetAllDnsVerification = useCallback(() => {
|
||||||
|
setDnsVerified({
|
||||||
|
cname: false,
|
||||||
|
ns: false,
|
||||||
|
a: false
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dnsVerified,
|
||||||
|
checkDnsConfig,
|
||||||
|
resetDnsVerification,
|
||||||
|
resetAllDnsVerification
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDnsVerification;
|
||||||
267
src/hooks/useDomainConfig.js
Normal file
267
src/hooks/useDomainConfig.js
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { cleanDomainInput, isValidDomainFormat, validateDomainAvailability } from '../utils/domainUtils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing domain configuration
|
||||||
|
* @param {Object} initialConfig - Initial domain configuration
|
||||||
|
* @param {Function} resetAllDnsVerification - Function to reset all DNS verification
|
||||||
|
* @returns {Object} - Domain configuration state and methods
|
||||||
|
*/
|
||||||
|
const useDomainConfig = (initialConfig = {}, resetAllDnsVerification) => {
|
||||||
|
// Domain configuration state
|
||||||
|
const [config, setConfig] = useState({
|
||||||
|
useSubdomain: true,
|
||||||
|
useCustomDomain: false,
|
||||||
|
customDomain: '',
|
||||||
|
customSubdomain: '',
|
||||||
|
domainType: 'domain',
|
||||||
|
dnsMethod: 'cname',
|
||||||
|
...initialConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
// Domain validation state
|
||||||
|
const [validation, setValidation] = useState({
|
||||||
|
isValidating: false,
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract values for easier access
|
||||||
|
const {
|
||||||
|
useSubdomain,
|
||||||
|
useCustomDomain,
|
||||||
|
customDomain,
|
||||||
|
customSubdomain,
|
||||||
|
domainType,
|
||||||
|
dnsMethod
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isValidating,
|
||||||
|
isValidDomain,
|
||||||
|
validationMessage,
|
||||||
|
showDnsConfig
|
||||||
|
} = validation;
|
||||||
|
|
||||||
|
// Update a single field in domain config
|
||||||
|
const updateField = useCallback((field, value) => {
|
||||||
|
setConfig(prev => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Update validation state
|
||||||
|
const updateValidation = useCallback((updates) => {
|
||||||
|
setValidation(prev => ({
|
||||||
|
...prev,
|
||||||
|
...updates
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handler for use subdomain checkbox
|
||||||
|
const handleUseSubdomainChange = useCallback((e) => {
|
||||||
|
const newValue = e.target.checked;
|
||||||
|
|
||||||
|
updateField('useSubdomain', newValue);
|
||||||
|
|
||||||
|
// CRITICAL RULE: If CNAME record is selected, SiliconPin subdomain must be enabled
|
||||||
|
// If user tries to uncheck SiliconPin subdomain while using CNAME, disable custom domain
|
||||||
|
if (useCustomDomain && dnsMethod === 'cname' && !newValue) {
|
||||||
|
updateField('useCustomDomain', false);
|
||||||
|
|
||||||
|
// Since we're disabling custom domain, reset validation
|
||||||
|
updateValidation({
|
||||||
|
showDnsConfig: false,
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
isValidating: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also reset DNS verification
|
||||||
|
if (resetAllDnsVerification) {
|
||||||
|
resetAllDnsVerification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [useCustomDomain, dnsMethod, updateField, updateValidation, resetAllDnsVerification]);
|
||||||
|
|
||||||
|
// Handler for use custom domain checkbox
|
||||||
|
const handleUseCustomDomainChange = useCallback((e) => {
|
||||||
|
const newValue = e.target.checked;
|
||||||
|
|
||||||
|
updateField('useCustomDomain', newValue);
|
||||||
|
|
||||||
|
if (!newValue) {
|
||||||
|
// Reset validation when disabling custom domain
|
||||||
|
updateValidation({
|
||||||
|
showDnsConfig: false,
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
isValidating: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset DNS verification
|
||||||
|
if (resetAllDnsVerification) {
|
||||||
|
resetAllDnsVerification();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// CRITICAL RULE: Force SiliconPin subdomain to be checked if custom domain is checked
|
||||||
|
updateField('useSubdomain', true);
|
||||||
|
}
|
||||||
|
}, [updateField, updateValidation, resetAllDnsVerification]);
|
||||||
|
|
||||||
|
// Handler for domain type change
|
||||||
|
const handleDomainTypeChange = useCallback((e) => {
|
||||||
|
updateField('domainType', e.target.value);
|
||||||
|
|
||||||
|
// Reset validation when changing domain type
|
||||||
|
updateValidation({
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset DNS verification
|
||||||
|
if (resetAllDnsVerification) {
|
||||||
|
resetAllDnsVerification();
|
||||||
|
}
|
||||||
|
}, [updateField, updateValidation, resetAllDnsVerification]);
|
||||||
|
|
||||||
|
// Handler for DNS method change
|
||||||
|
const handleDnsMethodChange = useCallback((e) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
|
||||||
|
updateField('dnsMethod', newValue);
|
||||||
|
|
||||||
|
// CRITICAL RULE: If changing to CNAME, ensure SiliconPin subdomain is enabled
|
||||||
|
if (newValue === 'cname') {
|
||||||
|
updateField('useSubdomain', true);
|
||||||
|
}
|
||||||
|
}, [updateField]);
|
||||||
|
|
||||||
|
// Handler for domain input change
|
||||||
|
const handleDomainChange = useCallback((e) => {
|
||||||
|
const cleanedValue = cleanDomainInput(e.target.value);
|
||||||
|
updateField('customDomain', cleanedValue);
|
||||||
|
|
||||||
|
// Reset domain validation when input changes
|
||||||
|
if (isValidDomain) {
|
||||||
|
updateValidation({
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [updateField, updateValidation, isValidDomain]);
|
||||||
|
|
||||||
|
// Handler for subdomain input change
|
||||||
|
const handleSubdomainChange = useCallback((e) => {
|
||||||
|
const cleanedValue = cleanDomainInput(e.target.value);
|
||||||
|
updateField('customSubdomain', cleanedValue);
|
||||||
|
|
||||||
|
// Reset domain validation when input changes
|
||||||
|
if (isValidDomain) {
|
||||||
|
updateValidation({
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [updateField, updateValidation, isValidDomain]);
|
||||||
|
|
||||||
|
// Validate domain
|
||||||
|
const validateDomain = useCallback(async () => {
|
||||||
|
const domain = domainType === 'domain' ? customDomain : customSubdomain;
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
updateValidation({
|
||||||
|
validationMessage: 'Please enter a domain name.',
|
||||||
|
isValidDomain: false,
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial validation: check format with regex
|
||||||
|
if (!isValidDomainFormat(domain)) {
|
||||||
|
updateValidation({
|
||||||
|
validationMessage: 'Domain format is invalid. Please check your entry.',
|
||||||
|
isValidDomain: false,
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show validation in progress
|
||||||
|
updateValidation({
|
||||||
|
isValidating: true,
|
||||||
|
validationMessage: '',
|
||||||
|
showDnsConfig: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate with API
|
||||||
|
const result = await validateDomainAvailability(domain, domainType);
|
||||||
|
|
||||||
|
updateValidation({
|
||||||
|
isValidating: false,
|
||||||
|
isValidDomain: result.isValid,
|
||||||
|
validationMessage: result.message,
|
||||||
|
showDnsConfig: result.isValid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset DNS verification when domain is validated
|
||||||
|
if (result.isValid && resetAllDnsVerification) {
|
||||||
|
resetAllDnsVerification();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
domainType,
|
||||||
|
customDomain,
|
||||||
|
customSubdomain,
|
||||||
|
updateValidation,
|
||||||
|
resetAllDnsVerification
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reset validation when disabling custom domain
|
||||||
|
useEffect(() => {
|
||||||
|
if (!useCustomDomain) {
|
||||||
|
updateValidation({
|
||||||
|
showDnsConfig: false,
|
||||||
|
isValidDomain: false,
|
||||||
|
validationMessage: '',
|
||||||
|
isValidating: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [useCustomDomain, updateValidation]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State values
|
||||||
|
config,
|
||||||
|
validation,
|
||||||
|
useSubdomain,
|
||||||
|
useCustomDomain,
|
||||||
|
customDomain,
|
||||||
|
customSubdomain,
|
||||||
|
domainType,
|
||||||
|
dnsMethod,
|
||||||
|
isValidating,
|
||||||
|
isValidDomain,
|
||||||
|
validationMessage,
|
||||||
|
showDnsConfig,
|
||||||
|
|
||||||
|
// Update functions
|
||||||
|
updateField,
|
||||||
|
updateValidation,
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
handleUseSubdomainChange,
|
||||||
|
handleUseCustomDomainChange,
|
||||||
|
handleDomainTypeChange,
|
||||||
|
handleDnsMethodChange,
|
||||||
|
handleDomainChange,
|
||||||
|
handleSubdomainChange,
|
||||||
|
validateDomain
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDomainConfig;
|
||||||
57
src/hooks/useFormValidation.js
Normal file
57
src/hooks/useFormValidation.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for form validation based on critical business rules
|
||||||
|
* @param {Object} domainConfig - Domain configuration state
|
||||||
|
* @param {Object} validation - Domain validation state
|
||||||
|
* @param {Object} dnsVerified - DNS verification state
|
||||||
|
* @returns {Object} - Form validation state and methods
|
||||||
|
*/
|
||||||
|
const useFormValidation = (domainConfig, validation, dnsVerified) => {
|
||||||
|
const [formValid, setFormValid] = useState(true);
|
||||||
|
|
||||||
|
const { useCustomDomain, dnsMethod } = domainConfig;
|
||||||
|
const { isValidDomain } = validation;
|
||||||
|
|
||||||
|
// Validate form
|
||||||
|
const validateForm = useCallback(() => {
|
||||||
|
let valid = true;
|
||||||
|
|
||||||
|
// For custom domain, require domain validation and DNS verification
|
||||||
|
if (useCustomDomain) {
|
||||||
|
// First requirement: domain must be validated successfully
|
||||||
|
if (!isValidDomain) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
// Second requirement: appropriate DNS verification must pass
|
||||||
|
else if (dnsMethod === 'cname' && dnsVerified.cname !== true) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
else if (dnsMethod === 'ns' && dnsVerified.ns !== true) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormValid(valid);
|
||||||
|
return valid;
|
||||||
|
}, [useCustomDomain, isValidDomain, dnsMethod, dnsVerified.cname, dnsVerified.ns]);
|
||||||
|
|
||||||
|
// Validate form when relevant state changes
|
||||||
|
useEffect(() => {
|
||||||
|
validateForm();
|
||||||
|
}, [
|
||||||
|
useCustomDomain,
|
||||||
|
isValidDomain,
|
||||||
|
dnsMethod,
|
||||||
|
dnsVerified.cname,
|
||||||
|
dnsVerified.ns,
|
||||||
|
validateForm
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
formValid,
|
||||||
|
validateForm
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFormValidation;
|
||||||
36
src/hooks/useToast.js
Normal file
36
src/hooks/useToast.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook for managing toast notifications
|
||||||
|
* @param {number} duration - Duration in ms to show the toast (default: 3000)
|
||||||
|
* @returns {Object} - Toast state and methods
|
||||||
|
*/
|
||||||
|
const useToast = (duration = 3000) => {
|
||||||
|
const [toast, setToast] = useState({
|
||||||
|
visible: false,
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show a toast notification
|
||||||
|
const showToast = useCallback((message) => {
|
||||||
|
setToast({ visible: true, message });
|
||||||
|
|
||||||
|
// Auto-hide the toast after the specified duration
|
||||||
|
setTimeout(() => {
|
||||||
|
setToast({ visible: false, message: '' });
|
||||||
|
}, duration);
|
||||||
|
}, [duration]);
|
||||||
|
|
||||||
|
// Hide the toast notification
|
||||||
|
const hideToast = useCallback(() => {
|
||||||
|
setToast({ visible: false, message: '' });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
toast,
|
||||||
|
showToast,
|
||||||
|
hideToast
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useToast;
|
||||||
@@ -133,7 +133,7 @@ const contactSchema = {
|
|||||||
<span class="text-neutral-300 font-medium">Sunday:</span>
|
<span class="text-neutral-300 font-medium">Sunday:</span>
|
||||||
<span class="text-white">Closed</span>
|
<span class="text-white">Closed</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-3 border-t border-neutral-700 flex items-center justify-between">
|
<div class="pt-3 border-t border-neutral-700">
|
||||||
<span class="text-neutral-300">Technical Support:</span>
|
<span class="text-neutral-300">Technical Support:</span>
|
||||||
<span class="text-[#6d9e37] font-semibold block mt-1">24/7</span>
|
<span class="text-[#6d9e37] font-semibold block mt-1">24/7</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from "../../layouts/Layout.astro"
|
|
||||||
---
|
|
||||||
<Layout title="">
|
|
||||||
<div>
|
|
||||||
<pre id="get-response">Adding domain, please wait...</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
fetch('/host-api/add-domain/')
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) throw new Error('Network error');
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
const resultElement = document.getElementById('get-response');
|
|
||||||
if (data.status === 'success') {
|
|
||||||
resultElement.textContent = `✅ ${data.message}\n${data.output || ''}`;
|
|
||||||
} else {
|
|
||||||
resultElement.textContent = `❌ ${data.message}`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
document.getElementById('get-response').textContent = `❌ Fetch Error: ${error.message}`;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</Layout>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from "../../layouts/Layout.astro"
|
|
||||||
---
|
|
||||||
<Layout title="">
|
|
||||||
<div>
|
|
||||||
<h2>Delete Domain</h2>
|
|
||||||
<form id="deleteDomainForm">
|
|
||||||
<input type="text" id="domainName" placeholder="example.com" required>
|
|
||||||
<button type="submit">Delete Domain</button>
|
|
||||||
</form>
|
|
||||||
<pre id="deleteResponse"></pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
document.getElementById('deleteDomainForm').addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const domain = document.getElementById('domainName').value;
|
|
||||||
const responseElement = document.getElementById('deleteResponse');
|
|
||||||
|
|
||||||
responseElement.textContent = "Deleting domain...";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/host-api/delete-domain/', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: `domain=${encodeURIComponent(domain)}`
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.status === 'success') {
|
|
||||||
responseElement.textContent = `✅ ${data.message}\nDomain: ${data.domain}`;
|
|
||||||
// Refresh domain list if needed
|
|
||||||
setTimeout(() => window.location.reload(), 1500);
|
|
||||||
} else {
|
|
||||||
responseElement.textContent = `❌ ${data.message}\nError: ${data.error || 'Unknown error'}`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
responseElement.textContent = `❌ Network Error: ${error.message}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</Layout>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from "../../layouts/Layout.astro"
|
|
||||||
---
|
|
||||||
<Layout title="">
|
|
||||||
<div>
|
|
||||||
<pre id="get-response">Loading domains...</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
fetch('/host-api/list-domain/')
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) throw new Error('Network error');
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
const pre = document.getElementById('get-response');
|
|
||||||
if (data.status === 'success') {
|
|
||||||
// Format domains as a table
|
|
||||||
let output = 'DOMAINS LIST:\n\n';
|
|
||||||
output += 'Domain'.padEnd(40) + 'IP'.padEnd(15) + 'SSL'.padEnd(5) + 'Status\n';
|
|
||||||
output += '-'.repeat(70) + '\n';
|
|
||||||
|
|
||||||
if (Object.keys(data.domains).length > 0) {
|
|
||||||
Object.entries(data.domains).forEach(([domain, info]) => {
|
|
||||||
output += `${domain.padEnd(40)}${info.IP.padEnd(15)}${info.SSL.padEnd(5)}${info.SUSPENDED === 'no' ? 'Active' : 'Suspended'}\n`;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
output += 'No domains found for this user\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.textContent = output;
|
|
||||||
} else {
|
|
||||||
pre.textContent = `❌ Error: ${data.message}\nDebug: ${data.debug || ''}\nJSON Error: ${data.json_error || ''}`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
document.getElementById('get-response').textContent = `❌ Fetch Error: ${error.message}`;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</Layout>
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
|
|
||||||
// Page-specific SEO metadata
|
// Page-specific SEO metadata
|
||||||
const pageTitle = "SiliconPin - Lets create some digital freedom";
|
const pageTitle = "SiliconPin - High-Performance Hosting Solutions";
|
||||||
const pageDescription = "SiliconPin - easy to deploy apps and tools, freedom oriented apps and tools, high-performance, hosting solutions for PHP, Node.js, Python, Kubernetes (K8s), and K3s, and technical support.";
|
const pageDescription = "SiliconPin offers reliable, high-performance hosting solutions for PHP, Node.js, Python, Kubernetes (K8s), and K3s with 24/7 technical support.";
|
||||||
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
|
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Layout from '../layouts/Layout.astro';
|
|||||||
|
|
||||||
// Page-specific SEO metadata
|
// Page-specific SEO metadata
|
||||||
const pageTitle = "Legal Agreement | SiliconPin";
|
const pageTitle = "Legal Agreement | SiliconPin";
|
||||||
const pageDescription = "Review SiliconPin's legal agreement for using our services. Find information about your legal rights and obligations.";
|
const pageDescription = "Review SiliconPin's legal agreement for using our hosting services. Find information about your legal rights and obligations.";
|
||||||
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
|
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -23,20 +23,6 @@ const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=8
|
|||||||
|
|
||||||
<div class="max-w-4xl mx-auto space-y-8">
|
<div class="max-w-4xl mx-auto space-y-8">
|
||||||
<section class="bg-neutral-800 rounded-lg p-6 sm:p-8 border border-neutral-700">
|
<section class="bg-neutral-800 rounded-lg p-6 sm:p-8 border border-neutral-700">
|
||||||
<h3 class="text-xl font-medium text-[#6d9e37]">TLDR;</h3>
|
|
||||||
<p class="text-neutral-300">
|
|
||||||
We are trying to create some digital freedom together. We avoid any vender locking - for you and us.
|
|
||||||
some of us from DWD Consultancy Services, having a quite experience manipulating data and data resilience.
|
|
||||||
Still we advise to keep a backup of your data periodically and use our automated backup and snapshot services (even AWS/GCP dont take data responsibility).
|
|
||||||
You are responsible for your content, and we are responsible for our services.
|
|
||||||
|
|
||||||
If something goes wrong, we will try to fix it. For SLA we will refund or we will part ways.
|
|
||||||
We are based in India, and Indian law applies to this agreement. We find some law funny and useless like the cookie acknowledgement.
|
|
||||||
<br>
|
|
||||||
we strongly feel just to be reasonable and fair to each other.
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
<br>
|
|
||||||
<p class="text-neutral-300">
|
<p class="text-neutral-300">
|
||||||
This Legal Agreement ("Agreement") is a binding contract between you ("Customer" or "you") and SiliconPin ("Company", "we", or "us") governing your use of our hosting services and related products (collectively, the "Services"). By using our Services, you acknowledge that you have read, understood, and agree to be bound by this Agreement.
|
This Legal Agreement ("Agreement") is a binding contract between you ("Customer" or "you") and SiliconPin ("Company", "we", or "us") governing your use of our hosting services and related products (collectively, the "Services"). By using our Services, you acknowledge that you have read, understood, and agree to be bound by this Agreement.
|
||||||
</p>
|
</p>
|
||||||
@@ -182,7 +168,7 @@ const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=8
|
|||||||
If you have any questions about this Legal Agreement, please contact us:
|
If you have any questions about this Legal Agreement, please contact us:
|
||||||
</p>
|
</p>
|
||||||
<div class="text-neutral-300">
|
<div class="text-neutral-300">
|
||||||
<p>Email: <a href="mailto:contact@siliconpin.com" class="text-[#6d9e37] hover:underline">contact@siliconpin.com</a></p>
|
<p>Email: <a href="mailto:legal@siliconpin.com" class="text-[#6d9e37] hover:underline">legal@siliconpin.com</a></p>
|
||||||
<p>Phone: +91-700-160-1485</p>
|
<p>Phone: +91-700-160-1485</p>
|
||||||
<p>Address: 121 Lalbari, GourBongo Road, Habra, W.B. 743271, India</p>
|
<p>Address: 121 Lalbari, GourBongo Road, Habra, W.B. 743271, India</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from "../layouts/Layout.astro";
|
|
||||||
const phpHello = `$_SESSION['userName']`;
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout title="Profile Page">
|
|
||||||
<div class="flex items-center justify-center min-h-screen bg-gray-700">
|
|
||||||
<div class="w-96 p-6 shadow-lg rounded-lg bg-white text-center">
|
|
||||||
<img class="w-24 h-24 rounded-full border-4 border-blue-500 mx-auto" src="/profile.jpg" alt="Profile Picture" />
|
|
||||||
<h2 class="text-2xl font-semibold mt-4" set:html={phpHello ? phpHello : 'User Name'}></h2>
|
|
||||||
<p class="text-gray-600">Frontend Developer</p>
|
|
||||||
<p class="text-gray-500 text-sm mt-2">"Building amazing UI experiences one component at a time."</p>
|
|
||||||
<div class="flex justify-center space-x-4 mt-4">
|
|
||||||
<button class="bg-blue-500 text-white px-4 py-2 rounded-lg">Follow</button>
|
|
||||||
<button class="bg-gray-200 text-black px-4 py-2 rounded-lg">Message</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
72
src/utils/domainUtils.js
Normal file
72
src/utils/domainUtils.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Clean domain input by removing http://, https://, www., and trailing slashes
|
||||||
|
* @param {string} input - The domain input string to clean
|
||||||
|
* @returns {string} - The cleaned domain string
|
||||||
|
*/
|
||||||
|
export const cleanDomainInput = (input) => {
|
||||||
|
return input
|
||||||
|
.replace(/^(https?:\/\/)?(www\.)?/i, '')
|
||||||
|
.replace(/\/+$/, '')
|
||||||
|
.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate domain format using regex
|
||||||
|
* @param {string} domain - The domain to validate
|
||||||
|
* @returns {boolean} - Whether the domain format is valid
|
||||||
|
*/
|
||||||
|
export const isValidDomainFormat = (domain) => {
|
||||||
|
if (!domain) return false;
|
||||||
|
return /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/.test(domain);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate domain availability with API
|
||||||
|
* @param {string} domain - The domain to validate
|
||||||
|
* @param {string} type - The domain type ('domain' or 'subdomain')
|
||||||
|
* @returns {Promise} - Promise resolving to validation result
|
||||||
|
*/
|
||||||
|
export const validateDomainAvailability = async (domain, type) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/validate-domain', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ domain, type })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Network error: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
isValid: data.status === "success",
|
||||||
|
message: data.status === "success"
|
||||||
|
? 'Domain is valid and registered.'
|
||||||
|
: 'Domain appears to be unregistered or unavailable.'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error validating domain:', error);
|
||||||
|
return {
|
||||||
|
isValid: false,
|
||||||
|
message: `Error checking domain: ${error.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy text to clipboard
|
||||||
|
* @param {string} text - Text to copy
|
||||||
|
* @returns {Promise} - Promise resolving to success or error
|
||||||
|
*/
|
||||||
|
export const copyToClipboard = async (text) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return { success: true };
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy:', err);
|
||||||
|
return { success: false, error: err.message };
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user