diff --git a/artifacts/teaboy-os/public/opengraph.jpg b/artifacts/teaboy-os/public/opengraph.jpg index ac85dc36..530741e0 100644 Binary files a/artifacts/teaboy-os/public/opengraph.jpg and b/artifacts/teaboy-os/public/opengraph.jpg differ diff --git a/artifacts/teaboy-os/src/locales/ar.json b/artifacts/teaboy-os/src/locales/ar.json index cd2f5b0a..0c166b33 100644 --- a/artifacts/teaboy-os/src/locales/ar.json +++ b/artifacts/teaboy-os/src/locales/ar.json @@ -19,7 +19,20 @@ "createAccount": "حساب جديد", "registrationClosed": "إنشاء الحسابات العامة مغلق حالياً. تواصل مع مدير النظام.", "displayNameAr": "الاسم (عربي)", - "displayNameEn": "الاسم (إنجليزي)" + "displayNameEn": "الاسم (إنجليزي)", + "subtitle": "سجّل دخولك للمتابعة", + "signingIn": "جارٍ الدخول...", + "noAccount": "ليس لديك حساب؟", + "showPassword": "إظهار كلمة المرور", + "hidePassword": "إخفاء كلمة المرور", + "errRequired": "الرجاء إدخال اسم المستخدم وكلمة المرور.", + "errInvalid": "اسم مستخدم أو كلمة مرور غير صحيحة.", + "heroTitle": "مرحباً بك في منصتك الموحدة.", + "heroSubtitle": "سجّل دخولك لإدارة كل شيء من مكان واحد.", + "feat1": "كل أدواتك في لوحة تحكم واحدة", + "feat2": "تحديثات لحظية", + "feat3": "وصول آمن بالجلسات", + "rights": "جميع الحقوق محفوظة." }, "home": { "myApps": "تطبيقاتي" diff --git a/artifacts/teaboy-os/src/locales/en.json b/artifacts/teaboy-os/src/locales/en.json index b10796f1..0e69a2ae 100644 --- a/artifacts/teaboy-os/src/locales/en.json +++ b/artifacts/teaboy-os/src/locales/en.json @@ -19,7 +19,20 @@ "createAccount": "Create Account", "registrationClosed": "Public registration is currently closed. Please contact the administrator.", "displayNameAr": "Name (Arabic)", - "displayNameEn": "Name (English)" + "displayNameEn": "Name (English)", + "subtitle": "Sign in to your account to continue", + "signingIn": "Signing in...", + "noAccount": "Don't have an account?", + "showPassword": "Show password", + "hidePassword": "Hide password", + "errRequired": "Please enter your username and password.", + "errInvalid": "Invalid username or password.", + "heroTitle": "Welcome to your unified workspace.", + "heroSubtitle": "Sign in to manage everything from one place.", + "feat1": "All your tools in one dashboard", + "feat2": "Real-time updates", + "feat3": "Secure session-based access", + "rights": "All rights reserved." }, "home": { "myApps": "My Apps" diff --git a/artifacts/teaboy-os/src/pages/login.tsx b/artifacts/teaboy-os/src/pages/login.tsx index 873ff2dc..44734330 100644 --- a/artifacts/teaboy-os/src/pages/login.tsx +++ b/artifacts/teaboy-os/src/pages/login.tsx @@ -1,31 +1,44 @@ -import { useState } from "react"; +import { useState, type FormEvent } from "react"; import { useTranslation } from "react-i18next"; import { useLocation } from "wouter"; -import { useLogin } from "@workspace/api-client-react"; +import { useLogin, getGetMeQueryKey } from "@workspace/api-client-react"; import { useQueryClient } from "@tanstack/react-query"; -import { getGetMeQueryKey } from "@workspace/api-client-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { useToast } from "@/hooks/use-toast"; import { useAppName, useAppSettings } from "@/hooks/use-app-settings"; import i18n from "@/i18n"; +import { + Sparkles, + CheckCircle2, + Eye, + EyeOff, + Loader2, + AlertCircle, +} from "lucide-react"; export default function LoginPage() { - const { t } = useTranslation(); + const { t, i18n: i18nInstance } = useTranslation(); + const lang = i18nInstance.language; + const dir = lang === "ar" ? "rtl" : "ltr"; const appName = useAppName(); const { data: settings } = useAppSettings(); const [, setLocation] = useLocation(); - const { toast } = useToast(); const queryClient = useQueryClient(); - const [form, setForm] = useState({ username: "", password: "" }); + + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(null); const login = useLogin(); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = (e: FormEvent) => { e.preventDefault(); + if (!username.trim() || !password) { + setError(t("auth.errRequired")); + return; + } + setError(null); login.mutate( - { data: { username: form.username, password: form.password } }, + { data: { username: username.trim(), password } }, { onSuccess: (user) => { if (user.preferredLanguage) { @@ -35,77 +48,217 @@ export default function LoginPage() { setLocation("/"); }, onError: () => { - toast({ - title: t("common.error"), - description: t("auth.login"), - variant: "destructive", - }); + setError(t("auth.errInvalid")); }, }, ); }; + const toggleLanguage = () => i18n.changeLanguage(lang === "ar" ? "en" : "ar"); + + const features = [ + t("auth.feat1"), + t("auth.feat2"), + t("auth.feat3"), + ]; + return ( -
-
-
-
{appName}
-

{t("auth.welcomeBack")}

+
+ {/* Left brand panel — hidden on small screens */} +
+
+ +
+
+
+ +
+ + {appName} + +
+ +
+

+ {t("auth.heroTitle")} +

+

+ {t("auth.heroSubtitle")} +

+
+ {features.map((feat) => ( +
+ + {feat} +
+ ))} +
+
-
-
- - setForm((f) => ({ ...f, username: e.target.value }))} - required - className="bg-white/70 border-slate-200 text-foreground placeholder:text-muted-foreground" - /> -
+
+ © {new Date().getFullYear()} {appName}. {t("auth.rights")} +
+
-
- - setForm((f) => ({ ...f, password: e.target.value }))} - required - className="bg-white/70 border-slate-200 text-foreground placeholder:text-muted-foreground" - /> -
- - - - - {settings?.registrationOpen && ( -
- -
- )} - -
+ {/* Right form panel */} +
+ {/* Language toggle */} +
+ + {/* Mobile brand */} +
+
+ +
+ {appName} +
+ +
+
+

+ {t("auth.welcomeBack")} +

+

+ {t("auth.subtitle")} +

+
+ +
+
+ {/* Username */} +
+ + { + setUsername(e.target.value); + setError(null); + }} + disabled={login.isPending} + className="block w-full h-10 rounded-md border border-input bg-background px-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary disabled:opacity-60" + /> +
+ + {/* Password */} +
+ +
+ { + setPassword(e.target.value); + setError(null); + }} + disabled={login.isPending} + className={`block w-full h-10 rounded-md border border-input bg-background px-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary disabled:opacity-60 ${ + dir === "rtl" ? "pl-10" : "pr-10" + }`} + /> + +
+
+ + {/* Error alert */} + {error && ( +
+ + {error} +
+ )} + + {/* Submit */} + + + {settings?.registrationOpen && ( +
+ {t("auth.noAccount")}{" "} + +
+ )} +
+
+
);