Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949 Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Co-authored-by: Nabarun <nabarun@deepstacksoft.com> Reviewed-on: #10 Co-authored-by: shreerang <shreerang@noreply.git.vdb.to> Co-committed-by: shreerang <shreerang@noreply.git.vdb.to>
111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
|
|
interface URLFormProps {
|
|
onSubmit: (url: string) => void;
|
|
disabled: boolean;
|
|
}
|
|
|
|
export default function URLForm({ onSubmit, disabled }: URLFormProps) {
|
|
// Get example URL from environment variables or use a default
|
|
const exampleUrl = process.env.NEXT_PUBLIC_EXAMPLE_URL || 'https://example.com';
|
|
const [url, setUrl] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// Trim the URL to remove any whitespace
|
|
const trimmedUrl = url.trim();
|
|
|
|
if (!trimmedUrl) {
|
|
setError('Please enter a URL');
|
|
return;
|
|
}
|
|
|
|
// Validate URL format
|
|
try {
|
|
const parsedUrl = new URL(trimmedUrl);
|
|
|
|
// Check for protocol
|
|
if (!parsedUrl.protocol.startsWith('http')) {
|
|
setError('URL must use HTTP or HTTPS protocol');
|
|
return;
|
|
}
|
|
|
|
// Check for hostname
|
|
if (!parsedUrl.hostname || parsedUrl.hostname.length < 3) {
|
|
setError('URL must contain a valid hostname');
|
|
return;
|
|
}
|
|
|
|
// Basic sanity check for common invalid URLs
|
|
if (parsedUrl.href === 'http://localhost' || parsedUrl.href === 'https://localhost') {
|
|
setError('Please enter a valid public URL, not localhost');
|
|
return;
|
|
}
|
|
|
|
// All validations passed
|
|
setError('');
|
|
onSubmit(trimmedUrl);
|
|
} catch (error) {
|
|
console.error(error);
|
|
setError('Please enter a valid URL (e.g., https://example.com)');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="w-full space-y-6">
|
|
<div className="flex flex-col">
|
|
<label htmlFor="url" className="mb-2 text-sm font-semibold" style={{ color: 'var(--foreground)' }}>
|
|
URL to Deploy
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
id="url"
|
|
type="text"
|
|
value={url}
|
|
onChange={(e) => setUrl(e.target.value)}
|
|
placeholder={exampleUrl}
|
|
className="w-full p-3 rounded-md transition-colors"
|
|
style={{
|
|
background: 'var(--card-bg)',
|
|
border: '1px solid var(--input-border)',
|
|
color: 'var(--foreground)',
|
|
opacity: disabled ? '0.6' : '1'
|
|
}}
|
|
disabled={disabled}
|
|
/>
|
|
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 opacity-60">
|
|
{url && (
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
|
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
|
|
</svg>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{error && (
|
|
<p className="mt-2 text-sm font-medium px-3 py-2 rounded-md" style={{ color: 'var(--error)', background: 'var(--error-light)' }}>
|
|
{error}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={disabled || !url}
|
|
className="w-full px-6 py-3 rounded-md transition-colors"
|
|
style={{
|
|
backgroundColor: (disabled || !url) ? 'var(--muted)' : 'var(--primary)',
|
|
color: 'var(--primary-foreground)',
|
|
opacity: (disabled || !url) ? '0.7' : '1',
|
|
}}
|
|
>
|
|
Deploy URL
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|