feat: add Space integration for user authentication and model management with OAuth support

This commit is contained in:
Junyan Qin
2025-12-26 00:35:47 +08:00
parent 7479545339
commit 8caab43b00
27 changed files with 5214 additions and 6156 deletions
+89 -5
View File
@@ -20,10 +20,10 @@ import {
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { httpClient } from '@/app/infra/http/HttpClient';
import { useRouter } from 'next/navigation';
import { Mail, Lock } from 'lucide-react';
import { Mail, Lock, Loader2 } from 'lucide-react';
import langbotIcon from '@/app/assets/langbot-logo.webp';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
@@ -38,6 +38,7 @@ const formSchema = (t: (key: string) => string) =>
export default function Register() {
const router = useRouter();
const { t } = useTranslation();
const [spaceLoading, setSpaceLoading] = useState(false);
const form = useForm<z.infer<ReturnType<typeof formSchema>>>({
resolver: zodResolver(formSchema(t)),
@@ -78,6 +79,26 @@ export default function Register() {
});
}
// Space OAuth redirect handler
const handleSpaceLoginClick = async () => {
setSpaceLoading(true);
try {
// Build the redirect URI to the OAuth callback page
const currentOrigin = window.location.origin;
const redirectUri = `${currentOrigin}/auth/space/callback`;
// Get the authorization URL from backend
const response = await httpClient.getSpaceAuthorizeUrl(redirectUri);
// Redirect to Space authorization page
window.location.href = response.authorize_url;
} catch {
toast.error(t('common.spaceLoginFailed'));
setSpaceLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
<Card className="w-[375px] shadow-lg dark:shadow-white/10">
@@ -100,7 +121,66 @@ export default function Register() {
{t('register.adminAccountNote')}
</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="space-y-6">
{/* Space Login - Recommended */}
<div className="space-y-3">
<Button
type="button"
className="w-full cursor-pointer"
onClick={handleSpaceLoginClick}
disabled={spaceLoading}
>
{spaceLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<svg
className="mr-2 h-4 w-4"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2L2 7L12 12L22 7L12 2Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2 17L12 22L22 17"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2 12L12 17L22 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
{t('register.initWithSpace')}
</Button>
<p className="text-xs text-center text-muted-foreground">
{t('register.spaceRecommended')}
</p>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-white dark:bg-card px-2 text-muted-foreground">
{t('common.or')}
</span>
</div>
</div>
{/* Local Account Registration */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
@@ -146,8 +226,12 @@ export default function Register() {
)}
/>
<Button type="submit" className="w-full mt-4 cursor-pointer">
{t('register.register')}
<Button
type="submit"
variant="outline"
className="w-full cursor-pointer"
>
{t('register.registerLocal')}
</Button>
</form>
</Form>