3 Commits

Author SHA1 Message Date
Arkadyuti Sarkar
b58068d108 refactored get started flow & code 2025-03-29 16:08:07 +05:30
Arkadyuti Sarkar
f9d1556ce9 added api 2025-03-28 16:48:41 +05:30
Kar
0900d51954 Merge pull request 'pull footer improvement to the stage' (#3) from staging into master
Reviewed-on: #3
2025-03-20 15:25:07 +00:00
33 changed files with 1679 additions and 336 deletions

2
.gitignore vendored
View File

@@ -5,7 +5,7 @@ dist/
# dependencies
node_modules/
public/host-api/
# logs
npm-debug.log*
yarn-debug.log*

View File

@@ -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

BIN
dist.zip

Binary file not shown.

View File

@@ -6,8 +6,7 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"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/"
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^4.2.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

View File

@@ -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.
.....................................................

View File

@@ -30,8 +30,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
const [dnsVerified, setDnsVerified] = useState({
cname: false,
ns: false,
a: false,
ip: false
a: false
});
// Form validation
@@ -62,7 +61,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
}
validateForm();
}, [useCustomDomain, dnsVerified.cname, dnsVerified.ns, dnsVerified.ns, domainType, dnsMethod]);
}, [useCustomDomain, dnsVerified.cname, dnsVerified.ns, domainType, dnsMethod]);
// Show toast notification
const showToast = (message) => {
@@ -111,8 +110,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
setDnsVerified({
cname: false,
ns: false,
a: false,
ip: false
a: false
});
} else {
// Force SiliconPin subdomain to be checked if custom domain is checked
@@ -189,10 +187,25 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
setValidationMessage('');
setShowDnsConfig(false);
// Simulate an API call to validate the domain
setTimeout(() => {
// Simulate a real domain check - in a real app this would be an API call
const checkResult = true; // Assume domain is valid for demo
fetch('/validate-domain', {
method: 'POST',
headers: {
'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);
setIsValidDomain(checkResult);
@@ -205,7 +218,14 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
}
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
@@ -248,10 +268,6 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
setFormValid(false);
return;
}
if (dnsMethod === 'ip' && !dnsVerified.ip) {
setFormValid(false);
return;
}
}
setFormValid(true);
@@ -267,7 +283,20 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
}
// 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!');
};
@@ -543,10 +572,9 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
{/* Domain Validation */}
<button
disabled={!customDomain}
type="button"
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
</button>
@@ -576,7 +604,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
{/* CNAME Record Option */}
<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
type="radio"
id="dns-cname"
@@ -616,14 +644,13 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
</button>
</div>
</div>
</label>
</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">
<label for="dns-ns" className="flex items-start cursor-pointer">
<div className="flex items-start">
<input
type="radio"
id="dns-ns"
@@ -676,63 +703,18 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
</button>
</div>
</div>
</label>
</div>
</div>
)}
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
<div className="p-4 bg-neutral-700/30 rounded-md border border-neutral-600 space-y-3">
<label for="dns-ip" className="flex items-start cursor-pointer">
<input
type="radio"
id="dns-ip"
name="dns-method"
value="ip"
checked={dnsMethod === 'ip'}
onChange={handleDnsMethodChange}
className="mt-1 mr-2"
/>
<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>
</label>
</div>
</>
)}
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
{/* Form Submit Button */}
<button
type="submit"
@@ -746,6 +728,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
Start the Deployment
</button>
</form>
<Toast visible={toast.visible} message={toast.message} />
</div>
);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View 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;

View File

@@ -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;

View File

@@ -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;

View 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;

View 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;

View 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;

View 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>
);
};

View 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>
);
};

View 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;

View 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;

View 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;

View 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
View 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;

View File

@@ -133,7 +133,7 @@ const contactSchema = {
<span class="text-neutral-300 font-medium">Sunday:</span>
<span class="text-white">Closed</span>
</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-[#6d9e37] font-semibold block mt-1">24/7</span>
</div>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -2,8 +2,8 @@
import Layout from '../layouts/Layout.astro';
// Page-specific SEO metadata
const pageTitle = "SiliconPin - Lets create some digital freedom";
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 pageTitle = "SiliconPin - High-Performance Hosting Solutions";
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";
---

View File

@@ -3,7 +3,7 @@ import Layout from '../layouts/Layout.astro';
// Page-specific SEO metadata
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";
---
@@ -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">
<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">
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>
@@ -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:
</p>
<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>Address: 121 Lalbari, GourBongo Road, Habra, W.B. 743271, India</p>
</div>

View File

@@ -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
View 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 };
}
};