aureon-os / frontend /src /components /AuthForm.tsx
GaryLeckey's picture
Deploy Aureon OS (deploy/selfhost stack: nginx edge + fusion API x2 + console + HNC organ + redis + postgres; paper mode) (part 18)
f568a4f verified
Raw History Blame Contribute Delete
10.5 kB
import { useState } from "react";
import { supabase } from "@/integrations/supabase/client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { toast } from "sonner";
import { z } from "zod";
import { Sparkles, Eye, EyeOff } from "lucide-react";
const emailSchema = z.string().email("Invalid email address");
const passwordSchema = z.string().min(8, "Password must be at least 8 characters");
interface AuthFormProps {
onSuccess: () => void;
}
export function AuthForm({ onSuccess }: AuthFormProps) {
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
// Sign In State
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// Sign Up State
const [signUpEmail, setSignUpEmail] = useState("");
const [signUpPassword, setSignUpPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [binanceApiKey, setBinanceApiKey] = useState("");
const [binanceApiSecret, setBinanceApiSecret] = useState("");
const [showApiSecret, setShowApiSecret] = useState(false);
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault();
try {
emailSchema.parse(signUpEmail);
passwordSchema.parse(signUpPassword);
if (signUpPassword !== confirmPassword) {
toast.error("Passwords do not match");
return;
}
setLoading(true);
const { data: authData, error: authError } = await supabase.auth.signUp({
email: signUpEmail,
password: signUpPassword,
options: {
emailRedirectTo: `${window.location.origin}/`,
}
});
if (authError) throw authError;
if (!authData.user) throw new Error("Failed to create account");
const { data: sessionData, error: sessionError } = await supabase.functions.invoke('create-aureon-session', {
body: {
userId: authData.user.id,
apiKey: binanceApiKey || null,
apiSecret: binanceApiSecret || null,
},
});
if (sessionError || !sessionData?.success) {
throw new Error(sessionError?.message || sessionData?.error || 'Session creation failed');
}
toast.success("Account created! Welcome.");
onSuccess();
} catch (error) {
if (error instanceof z.ZodError) {
toast.error(error.errors[0].message);
} else if (error instanceof Error) {
toast.error(error.message);
}
} finally {
setLoading(false);
}
};
const handleSignIn = async (e: React.FormEvent) => {
e.preventDefault();
try {
emailSchema.parse(email);
passwordSchema.parse(password);
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({
email,
password
});
if (error) throw error;
toast.success("Welcome back!");
onSuccess();
} catch (error) {
if (error instanceof z.ZodError) {
toast.error(error.errors[0].message);
} else if (error instanceof Error) {
toast.error(error.message);
}
} finally {
setLoading(false);
}
};
return (
<Card className="w-full max-w-md mx-auto border-border/50">
<CardHeader className="text-center space-y-2">
<div className="flex justify-center">
<div className="h-12 w-12 rounded-xl bg-primary flex items-center justify-center">
<Sparkles className="h-6 w-6 text-primary-foreground" />
</div>
</div>
<CardTitle className="text-2xl font-bold">AUREON</CardTitle>
<CardDescription>Sign in to access trade feeds</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="signin" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-4">
<TabsTrigger value="signin">Sign In</TabsTrigger>
<TabsTrigger value="signup">Sign Up</TabsTrigger>
</TabsList>
<TabsContent value="signin">
<form onSubmit={handleSignIn} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="signin-email">Email</Label>
<Input
id="signin-email"
type="email"
placeholder="your@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="signin-password">Password</Label>
<div className="relative">
<Input
id="signin-password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Signing in..." : "Sign In"}
</Button>
</form>
</TabsContent>
<TabsContent value="signup">
<form onSubmit={handleSignUp} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="signup-email">Email</Label>
<Input
id="signup-email"
type="email"
placeholder="your@email.com"
value={signUpEmail}
onChange={(e) => setSignUpEmail(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="signup-password">Password</Label>
<div className="relative">
<Input
id="signup-password"
type={showPassword ? "text" : "password"}
placeholder="Min. 8 characters"
value={signUpPassword}
onChange={(e) => setSignUpPassword(e.target.value)}
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password">Confirm Password</Label>
<Input
id="confirm-password"
type={showPassword ? "text" : "password"}
placeholder="Re-enter password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
className={confirmPassword && signUpPassword !== confirmPassword ? "border-destructive" : ""}
/>
{confirmPassword && signUpPassword !== confirmPassword && (
<p className="text-xs text-destructive">Passwords do not match</p>
)}
</div>
<div className="border-t border-border/50 pt-4 mt-4">
<p className="text-xs text-muted-foreground mb-3">
Binance API (optional - add later in Settings)
</p>
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="binance-api-key">API Key</Label>
<Input
id="binance-api-key"
type="text"
placeholder="Your Binance API Key"
value={binanceApiKey}
onChange={(e) => setBinanceApiKey(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="binance-api-secret">API Secret</Label>
<div className="relative">
<Input
id="binance-api-secret"
type={showApiSecret ? "text" : "password"}
placeholder="Your Binance API Secret"
value={binanceApiSecret}
onChange={(e) => setBinanceApiSecret(e.target.value)}
/>
<button
type="button"
onClick={() => setShowApiSecret(!showApiSecret)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
{showApiSecret ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
</div>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Creating account..." : "Sign Up"}
</Button>
</form>
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
}