Files
tower/apps/web/app/signup/SignupForm.tsx
T
2026-06-09 02:02:40 +05:30

143 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useAuth } from '../_lib/auth-context';
function slugify(input: string): string {
return input
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 40);
}
export function SignupForm({ redirect }: { redirect?: string }) {
const router = useRouter();
const { refresh } = useAuth();
const [tenantName, setTenantName] = useState('');
const [tenantSlug, setTenantSlug] = useState('');
const [slugTouched, setSlugTouched] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const computedSlug = slugify(tenantName);
const effectiveSlug = slugTouched ? tenantSlug : computedSlug;
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
if (effectiveSlug.length < 2) {
setError('Tenant name must produce a valid slug (lowercase letters, digits, dashes)');
return;
}
setSubmitting(true);
try {
const res = await fetch('/api/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tenantName, tenantSlug: effectiveSlug, email, password }),
credentials: 'include',
});
if (!res.ok) {
const data = (await res.json().catch(() => ({}))) as { message?: string };
const messages = Array.isArray((data as { message?: unknown }).message)
? ((data as unknown as { message: string[] }).message.join(', '))
: data.message;
setError(messages ?? 'Signup failed');
return;
}
await refresh();
router.push(redirect || '/');
} finally {
setSubmitting(false);
}
}
return (
<form onSubmit={onSubmit} className="space-y-3">
<label className="block text-sm">
<span className="font-medium">Community / organization name</span>
<input
type="text"
value={tenantName}
onChange={(e) => setTenantName(e.target.value)}
placeholder="Delhi Traders"
required
className="mt-1 w-full border rounded px-3 py-2"
/>
</label>
<label className="block text-sm">
<span className="font-medium">URL slug</span>
<input
type="text"
value={effectiveSlug}
onChange={(e) => {
setSlugTouched(true);
setTenantSlug(slugify(e.target.value));
}}
placeholder="delhi"
required
className="mt-1 w-full border rounded px-3 py-2 font-mono text-sm"
/>
<span className="block text-xs text-gray-500 mt-1">
Lowercase letters, digits, dashes. 240 chars.
</span>
</label>
<label className="block text-sm">
<span className="font-medium">Your email</span>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
className="mt-1 w-full border rounded px-3 py-2"
/>
</label>
<label className="block text-sm">
<span className="font-medium">Password</span>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="At least 8 characters"
minLength={8}
required
className="mt-1 w-full border rounded px-3 py-2"
/>
</label>
<label className="block text-sm">
<span className="font-medium">Confirm password</span>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
minLength={8}
required
className="mt-1 w-full border rounded px-3 py-2"
/>
</label>
{error && <p className="text-sm text-red-600">{error}</p>}
<button
type="submit"
disabled={submitting}
className="w-full px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{submitting ? 'Creating account…' : 'Create community'}
</button>
<p className="text-xs text-gray-500 text-center">
You&apos;ll be the first OWNER. You can invite others from settings later.
</p>
</form>
);
}