diff --git a/src/components/DomainSetupForm.jsx b/src/components/DomainSetupForm-old.jsx
similarity index 100%
rename from src/components/DomainSetupForm.jsx
rename to src/components/DomainSetupForm-old.jsx
diff --git a/src/components/DomainSetupForm/DeploymentOptions/AppDeployment.jsx b/src/components/DomainSetupForm/DeploymentOptions/AppDeployment.jsx
new file mode 100644
index 0000000..c6fd7ae
--- /dev/null
+++ b/src/components/DomainSetupForm/DeploymentOptions/AppDeployment.jsx
@@ -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 (
+
+
+
+
+ );
+};
+
+export default AppDeployment;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DeploymentOptions/SourceDeployment.jsx b/src/components/DomainSetupForm/DeploymentOptions/SourceDeployment.jsx
new file mode 100644
index 0000000..bc2fbc0
--- /dev/null
+++ b/src/components/DomainSetupForm/DeploymentOptions/SourceDeployment.jsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ {sourceType === 'private' && (
+
+
+
+
+
+
+
Your private key is used only for deploying and is never stored on our servers.
+
+ )}
+
+ );
+};
+
+export default SourceDeployment;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DeploymentOptions/StaticDeployment.jsx b/src/components/DomainSetupForm/DeploymentOptions/StaticDeployment.jsx
new file mode 100644
index 0000000..6754315
--- /dev/null
+++ b/src/components/DomainSetupForm/DeploymentOptions/StaticDeployment.jsx
@@ -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 (
+
+
+ Upload the zip file containing your static website
+
+
+
+
+
+
+
+ {fileName && (
+
+ Selected file: {fileName}
+
+ )}
+
+
+ );
+};
+
+export default StaticDeployment;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DeploymentOptions/TemplateDeployment.jsx b/src/components/DomainSetupForm/DeploymentOptions/TemplateDeployment.jsx
new file mode 100644
index 0000000..b1f344a
--- /dev/null
+++ b/src/components/DomainSetupForm/DeploymentOptions/TemplateDeployment.jsx
@@ -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 (
+
+
+
+
+ {/* Information box */}
+
+
+
âšī¸
+
+
+ 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.
+
+
+
+
+
+ {/* Template Preview */}
+
+
+ );
+};
+
+export default TemplateDeployment;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DeploymentOptions/index.jsx b/src/components/DomainSetupForm/DeploymentOptions/index.jsx
new file mode 100644
index 0000000..bfd679d
--- /dev/null
+++ b/src/components/DomainSetupForm/DeploymentOptions/index.jsx
@@ -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 (
+
+ {/* Deployment Type Selection */}
+
+
+
+
+
+ {/* Render different components based on deployment type */}
+ {type === 'app' && (
+
+ )}
+
+ {type === 'sample-web-app' && (
+
+ )}
+
+ {type === 'source' && (
+
+ )}
+
+ {type === 'static' && (
+
+ )}
+
+ );
+};
+
+export default DeploymentOptions;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DomainConfiguration/CustomDomain.jsx b/src/components/DomainSetupForm/DomainConfiguration/CustomDomain.jsx
new file mode 100644
index 0000000..c3a876b
--- /dev/null
+++ b/src/components/DomainSetupForm/DomainConfiguration/CustomDomain.jsx
@@ -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 (
+
+ {/* Domain Type Selection */}
+
+
+ {/* Domain Input */}
+ {domainType === 'domain' ? (
+
+
+
+ Enter domain without http://, www, or trailing slashes (example.com). You can configure www or other subdomains later.
+
+
+ ) : (
+
+
+
+ Enter the full subdomain without http:// or trailing slashes. www and protocol prefixes will be automatically removed.
+
+
+ )}
+
+ {/* Domain Validation */}
+
+
+ {/* Validation Status */}
+ {isValidating && (
+
+
+
+
Verifying domain registration and availability...
+
+
+ )}
+
+ {!isValidating && validationMessage && (
+
+
+ {validationMessage}
+
+
+ )}
+
+ {/* DNS Configuration Options */}
+ {showDnsConfig && (
+
+ )}
+
+ );
+};
+
+export default CustomDomain;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DomainConfiguration/DnsConfiguration.jsx b/src/components/DomainSetupForm/DomainConfiguration/DnsConfiguration.jsx
new file mode 100644
index 0000000..0983266
--- /dev/null
+++ b/src/components/DomainSetupForm/DomainConfiguration/DnsConfiguration.jsx
@@ -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 (
+
+
Connect Your Domain
+
+ {/* CNAME Record Option */}
+
+
+
+
+
+
Point your domain to our SiliconPin subdomain
+
+
+
+ {defaultSubdomain}.subdomain.siliconpin.com
+
+
+
+
+
+
+
+
+
+
+ {/* Nameserver Option (only for full domains, not subdomains) */}
+ {domainType === 'domain' && (
+
+
+
+
+
+
Update your domain's nameservers to use ours
+
+
+
+
ns1.siliconpin.com
+
+
+
+
+
ns2.siliconpin.com
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default DnsConfiguration;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/DomainConfiguration/index.jsx b/src/components/DomainSetupForm/DomainConfiguration/index.jsx
new file mode 100644
index 0000000..1e35d6f
--- /dev/null
+++ b/src/components/DomainSetupForm/DomainConfiguration/index.jsx
@@ -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 (
+
+
Destination
+
+
+ {/* SiliconPin Subdomain */}
+
+
+
+
+
+
+ .subdomain.siliconpin.com
+
+
+
+
+ {/* Custom Domain */}
+
+
+
+
+
+ {useCustomDomain && (
+
+ )}
+
+
+
+
+ );
+};
+
+export default DomainConfiguration;
\ No newline at end of file
diff --git a/src/components/DomainSetupForm/index.jsx b/src/components/DomainSetupForm/index.jsx
new file mode 100644
index 0000000..ff10f68
--- /dev/null
+++ b/src/components/DomainSetupForm/index.jsx
@@ -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 (
+
+
+
+
+
+ );
+};
+
+export default DomainSetupForm;
\ No newline at end of file
diff --git a/src/components/atoms/SelectWithLabel/SelectWithLabel.tsx b/src/components/atoms/SelectWithLabel/SelectWithLabel.tsx
new file mode 100644
index 0000000..bb2173c
--- /dev/null
+++ b/src/components/atoms/SelectWithLabel/SelectWithLabel.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+
+const SelectWithLabel = ({value, onChange}: any)=>{
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default SelectWithLabel;
\ No newline at end of file
diff --git a/src/components/atoms/SelectWithLabel/index.tsx b/src/components/atoms/SelectWithLabel/index.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/shared/TemplatePreview.jsx b/src/components/shared/TemplatePreview.jsx
new file mode 100644
index 0000000..2ae9190
--- /dev/null
+++ b/src/components/shared/TemplatePreview.jsx
@@ -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 (
+
+ {/* Template preview image */}
+
+

+
+
+
+ {/* Template details */}
+
+
{template.description}
+
+
+
Features:
+
+ {template.features.map((feature, index) => (
+ -
+ â
+ {feature}
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/shared/Toast.jsx b/src/components/shared/Toast.jsx
new file mode 100644
index 0000000..9a5a008
--- /dev/null
+++ b/src/components/shared/Toast.jsx
@@ -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 (
+
+
+ {icon}
+ {message}
+
+
+ );
+};
diff --git a/src/hooks/useDeploymentConfig.js b/src/hooks/useDeploymentConfig.js
new file mode 100644
index 0000000..cf20959
--- /dev/null
+++ b/src/hooks/useDeploymentConfig.js
@@ -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;
\ No newline at end of file
diff --git a/src/hooks/useDnsVerification.js b/src/hooks/useDnsVerification.js
new file mode 100644
index 0000000..8be0911
--- /dev/null
+++ b/src/hooks/useDnsVerification.js
@@ -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;
\ No newline at end of file
diff --git a/src/hooks/useDomainConfig.js b/src/hooks/useDomainConfig.js
new file mode 100644
index 0000000..e3d458c
--- /dev/null
+++ b/src/hooks/useDomainConfig.js
@@ -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;
\ No newline at end of file
diff --git a/src/hooks/useFormValidation.js b/src/hooks/useFormValidation.js
new file mode 100644
index 0000000..b56c39c
--- /dev/null
+++ b/src/hooks/useFormValidation.js
@@ -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;
\ No newline at end of file
diff --git a/src/hooks/useToast.js b/src/hooks/useToast.js
new file mode 100644
index 0000000..b732f65
--- /dev/null
+++ b/src/hooks/useToast.js
@@ -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;
\ No newline at end of file
diff --git a/src/utils/domainUtils.js b/src/utils/domainUtils.js
new file mode 100644
index 0000000..ce4965e
--- /dev/null
+++ b/src/utils/domainUtils.js
@@ -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 };
+ }
+};
\ No newline at end of file