parent
4b6e4af86d
commit
5cd02bfbde
|
@ -1,30 +0,0 @@
|
||||||
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;
|
|
|
@ -1,79 +0,0 @@
|
||||||
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;
|
|
|
@ -1,53 +0,0 @@
|
||||||
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;
|
|
|
@ -1,46 +0,0 @@
|
||||||
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;
|
|
|
@ -1,98 +0,0 @@
|
||||||
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;
|
|
|
@ -1,155 +0,0 @@
|
||||||
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;
|
|
|
@ -1,161 +0,0 @@
|
||||||
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;
|
|
|
@ -1,91 +0,0 @@
|
||||||
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;
|
|
|
@ -1,136 +0,0 @@
|
||||||
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;
|
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
|
@ -1,80 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,103 +0,0 @@
|
||||||
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;
|
|
|
@ -1,62 +0,0 @@
|
||||||
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;
|
|
|
@ -1,267 +0,0 @@
|
||||||
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;
|
|
|
@ -1,57 +0,0 @@
|
||||||
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;
|
|
|
@ -1,36 +0,0 @@
|
||||||
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;
|
|
|
@ -1,72 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 };
|
|
||||||
}
|
|
||||||
};
|
|
Loading…
Reference in New Issue