ubiquitous-invention/apps/web/app/(auth)/sign-up/page.tsx
Randall Stillwell a508ece6e7 feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:

- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)

Made-with: Cursor
2026-03-26 22:39:16 -05:00

148 lines
5.6 KiB
TypeScript

"use client";
import { useState } from "react";
import Link from "next/link";
import { Loader2, Mail, Sparkles, User, Lock } from "lucide-react";
import { cn } from "@/lib/utils";
export default function SignUpPage() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string | null>(null);
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setMessage(null);
setLoading(true);
try {
await new Promise((r) => setTimeout(r, 600));
setMessage("Thanks — account creation will connect to your API soon. For now, use dev sign-in.");
} finally {
setLoading(false);
}
}
return (
<div
className={cn(
"rounded-2xl border border-border/60 bg-card/80 p-8 shadow-xl shadow-[hsl(var(--teal)/0.06)] backdrop-blur-md",
"ring-1 ring-[hsl(var(--teal)/0.15)]",
)}
>
<div className="mb-8 text-center">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-[hsl(var(--teal))] to-[hsl(var(--primary))] text-primary-foreground shadow-lg">
<Sparkles className="h-6 w-6" aria-hidden />
</div>
<h1 className="text-2xl font-semibold tracking-tight text-foreground">Join Tasks</h1>
<p className="mt-2 text-sm text-muted-foreground">Create your workspace registration is a preview for now.</p>
</div>
<form onSubmit={onSubmit} className="space-y-5">
{message ? (
<div
role="status"
className="rounded-lg border border-[hsl(var(--teal)/0.35)] bg-[hsl(var(--teal)/0.08)] px-3 py-2 text-sm text-foreground"
>
{message}
</div>
) : null}
<div className="space-y-2">
<label htmlFor="name" className="text-sm font-medium text-foreground">
Name
</label>
<div className="relative">
<User className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
id="name"
name="name"
type="text"
autoComplete="name"
required
value={name}
onChange={(e) => setName(e.target.value)}
className={cn(
"w-full rounded-lg border border-input bg-background py-2.5 pl-10 pr-3 text-sm outline-none transition",
"placeholder:text-muted-foreground focus:border-[hsl(var(--teal))] focus:ring-2 focus:ring-[hsl(var(--teal)/0.25)]",
)}
placeholder="Alex Doe"
/>
</div>
</div>
<div className="space-y-2">
<label htmlFor="email" className="text-sm font-medium text-foreground">
Email
</label>
<div className="relative">
<Mail className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className={cn(
"w-full rounded-lg border border-input bg-background py-2.5 pl-10 pr-3 text-sm outline-none transition",
"placeholder:text-muted-foreground focus:border-[hsl(var(--teal))] focus:ring-2 focus:ring-[hsl(var(--teal)/0.25)]",
)}
placeholder="you@example.com"
/>
</div>
</div>
<div className="space-y-2">
<label htmlFor="password" className="text-sm font-medium text-foreground">
Password
</label>
<div className="relative">
<Lock className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<input
id="password"
name="password"
type="password"
autoComplete="new-password"
required
minLength={8}
value={password}
onChange={(e) => setPassword(e.target.value)}
className={cn(
"w-full rounded-lg border border-input bg-background py-2.5 pl-10 pr-3 text-sm outline-none transition",
"placeholder:text-muted-foreground focus:border-[hsl(var(--teal))] focus:ring-2 focus:ring-[hsl(var(--teal)/0.25)]",
)}
placeholder="At least 8 characters"
/>
</div>
</div>
<button
type="submit"
disabled={loading}
className={cn(
"flex w-full items-center justify-center gap-2 rounded-lg py-2.5 text-sm font-medium text-primary-foreground transition",
"bg-gradient-to-r from-[hsl(var(--primary))] to-[hsl(var(--teal))] hover:opacity-[0.96]",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[hsl(var(--primary))]",
"disabled:pointer-events-none disabled:opacity-60",
)}
>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
Create account
</button>
</form>
<p className="mt-8 text-center text-sm text-muted-foreground">
Already have an account?{" "}
<Link
href="/sign-in"
className="font-medium text-[hsl(var(--primary))] underline-offset-4 hover:underline"
>
Sign in
</Link>
</p>
</div>
);
}