mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-08-18 21:56:17 +00:00
fac4492050
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/7a5b96f8-9042-44cf-bea7-814540e9a60d.jpg
307 lines
13 KiB
TypeScript
307 lines
13 KiB
TypeScript
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useForm } from "react-hook-form";
|
|
import { z } from "zod";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { useOrganization } from "@/context/organization-context";
|
|
import { Redirect } from "wouter";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { ThemeToggle } from "@/components/shared/theme-toggle";
|
|
import { useTheme } from "@/hooks/use-theme";
|
|
import { Loader2 } from "lucide-react";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
const loginSchema = z.object({
|
|
username: z.string().min(3, "Username must be at least 3 characters"),
|
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
});
|
|
|
|
const registerSchema = z.object({
|
|
username: z.string().min(3, "Username must be at least 3 characters"),
|
|
email: z.string().email("Please enter a valid email"),
|
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
fullName: z.string().optional(),
|
|
organizationId: z.string().optional(),
|
|
});
|
|
|
|
export default function AuthPage() {
|
|
const { user, loginMutation, registerMutation } = useAuth();
|
|
const { theme } = useTheme();
|
|
const { organizations, isLoading: orgsLoading } = useOrganization();
|
|
|
|
const loginForm = useForm<z.infer<typeof loginSchema>>({
|
|
resolver: zodResolver(loginSchema),
|
|
defaultValues: {
|
|
username: "",
|
|
password: "",
|
|
},
|
|
});
|
|
|
|
const registerForm = useForm<z.infer<typeof registerSchema>>({
|
|
resolver: zodResolver(registerSchema),
|
|
defaultValues: {
|
|
username: "",
|
|
email: "",
|
|
password: "",
|
|
fullName: "",
|
|
organizationId: undefined,
|
|
},
|
|
});
|
|
|
|
const onLoginSubmit = (values: z.infer<typeof loginSchema>) => {
|
|
loginMutation.mutate(values);
|
|
};
|
|
|
|
const onRegisterSubmit = (values: z.infer<typeof registerSchema>) => {
|
|
registerMutation.mutate(values);
|
|
};
|
|
|
|
// Redirect if already logged in
|
|
if (user) {
|
|
return <Redirect to="/" />;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen flex flex-col lg:flex-row">
|
|
{/* Auth Form */}
|
|
<div className="flex-1 flex items-center justify-center p-6 bg-background">
|
|
<div className="absolute top-4 right-4">
|
|
<ThemeToggle />
|
|
</div>
|
|
|
|
<Card className="w-full max-w-md">
|
|
<CardHeader className="space-y-1">
|
|
<div className="flex items-center mb-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="w-6 h-6 text-primary mr-2"
|
|
>
|
|
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
|
</svg>
|
|
<span className="font-semibold text-xl">DynamoDNS</span>
|
|
</div>
|
|
<CardTitle className="text-2xl">Welcome</CardTitle>
|
|
<CardDescription>
|
|
Sign in to your account or create a new one
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs defaultValue="login" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2 mb-4">
|
|
<TabsTrigger value="login">Login</TabsTrigger>
|
|
<TabsTrigger value="register">Register</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="login">
|
|
<Form {...loginForm}>
|
|
<form onSubmit={loginForm.handleSubmit(onLoginSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={loginForm.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Username or Email</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Enter your username or email" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={loginForm.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" placeholder="••••••••" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<Button
|
|
type="submit"
|
|
className="w-full"
|
|
disabled={loginMutation.isPending}
|
|
>
|
|
{loginMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Logging in...
|
|
</>
|
|
) : (
|
|
"Login"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="register">
|
|
<Form {...registerForm}>
|
|
<form onSubmit={registerForm.handleSubmit(onRegisterSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={registerForm.control}
|
|
name="username"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Username</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Choose a username" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={registerForm.control}
|
|
name="email"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Email</FormLabel>
|
|
<FormControl>
|
|
<Input type="email" placeholder="Enter your email" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={registerForm.control}
|
|
name="fullName"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Full Name (Optional)</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Enter your full name" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={registerForm.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Password</FormLabel>
|
|
<FormControl>
|
|
<Input type="password" placeholder="Create a password" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={registerForm.control}
|
|
name="organizationId"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Organization</FormLabel>
|
|
<FormControl>
|
|
<Select
|
|
disabled={orgsLoading}
|
|
onValueChange={(value) => field.onChange(value)}
|
|
value={field.value || ""}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select an organization (optional)" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{organizations.map((org) => (
|
|
<SelectItem key={org.id} value={org.id.toString()}>
|
|
{org.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<Button
|
|
type="submit"
|
|
className="w-full"
|
|
disabled={registerMutation.isPending}
|
|
>
|
|
{registerMutation.isPending ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Creating account...
|
|
</>
|
|
) : (
|
|
"Create Account"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</Form>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Hero Section */}
|
|
<div className="hidden lg:flex lg:flex-1 bg-primary text-primary-foreground">
|
|
<div className="max-w-lg mx-auto p-10 flex flex-col justify-center">
|
|
<h1 className="text-4xl font-bold mb-6">Dynamic DNS Management System</h1>
|
|
<p className="text-xl mb-8">
|
|
Keep your DNS records up to date across multiple providers with our powerful management platform.
|
|
</p>
|
|
<div className="space-y-6">
|
|
<div className="flex items-start">
|
|
<div className="mr-4 p-2 rounded-full bg-primary-foreground/10">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
|
|
<path d="m9 12 2 2 4-4"></path>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-semibold">Automatic Updates</h3>
|
|
<p>Keep your DNS records in sync with your changing IP addresses.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start">
|
|
<div className="mr-4 p-2 rounded-full bg-primary-foreground/10">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect>
|
|
<path d="M3 9h18"></path>
|
|
<path d="M9 21V9"></path>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-semibold">Multi-Provider Support</h3>
|
|
<p>Manage DNS records across Cloudflare, Route53, GoDaddy, and more.</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start">
|
|
<div className="mr-4 p-2 rounded-full bg-primary-foreground/10">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M3 3v18h18"></path>
|
|
<path d="m19 9-5 5-4-4-3 3"></path>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-semibold">Comprehensive Metrics</h3>
|
|
<p>View performance metrics and history for all your DNS records.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|