style: login page and logo

This commit is contained in:
Julien Froidefond
2025-03-04 21:35:51 +01:00
parent c36bbf98cc
commit 727d6301ae
6 changed files with 103 additions and 7617 deletions

7593
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "12.4.10",
"i18next": "^24.2.2", "i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.0.4", "i18next-browser-languagedetector": "^8.0.4",
"lucide-react": "^0.476.0", "lucide-react": "^0.476.0",

View File

@@ -5,6 +5,7 @@ import { RegisterForm } from "@/components/auth/RegisterForm";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import LanguageSelector from "@/components/LanguageSelector"; import LanguageSelector from "@/components/LanguageSelector";
import { motion } from "framer-motion";
interface LoginContentProps { interface LoginContentProps {
searchParams: { searchParams: {
@@ -19,20 +20,30 @@ export function LoginContent({ searchParams }: LoginContentProps) {
return ( return (
<div className="container relative min-h-screen flex-col items-center justify-center grid lg:max-w-none lg:grid-cols-2 lg:px-0"> <div className="container relative min-h-screen flex-col items-center justify-center grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<div className="absolute top-4 right-4 z-50"> <motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="absolute top-4 right-4 z-50 hover:scale-105 transition-transform"
>
<LanguageSelector /> <LanguageSelector />
</div> </motion.div>
<div className="relative hidden h-full flex-col bg-slate-800/80 p-10 text-white lg:flex dark:border-r overflow-hidden"> <div className="relative hidden h-full flex-col bg-slate-900 p-10 text-white lg:flex dark:border-r overflow-hidden">
<div <div
className="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-40 transition-opacity duration-200 ease-in-out" className="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-40 transition-opacity duration-200 ease-in-out hover:opacity-50 transform hover:scale-105 transition-transform duration-10000 ease-linear"
style={{ style={{
backgroundImage: "url('/images/login-bg.jpg')", backgroundImage: "url('/images/login-bg.jpg')",
}} }}
/> />
<div className="absolute inset-0 bg-gradient-to-b from-slate-800/20 to-slate-800/70" /> <div className="absolute inset-0 bg-gradient-to-b from-slate-900/30 via-slate-900/50 to-slate-900/90" />
<div className="relative z-20 flex items-center text-lg font-medium"> <motion.div
<svg initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="relative z-20 flex items-center text-lg font-medium"
>
<motion.svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
@@ -41,37 +52,83 @@ export function LoginContent({ searchParams }: LoginContentProps) {
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
className="mr-2 h-6 w-6" className="mr-2 h-6 w-6"
whileHover={{ rotate: 180 }}
transition={{ duration: 0.3 }}
> >
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" /> <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg> </motion.svg>
<span className="text-2xl font-bold bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">
StripStream StripStream
</div> </span>
<div className="relative z-20 mt-auto"> </motion.div>
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="relative z-20 mt-auto"
>
<blockquote className="space-y-2"> <blockquote className="space-y-2">
<p className="text-lg">{t("login.description")}</p> <p className="text-xl font-light leading-relaxed tracking-wide text-gray-200">
{t("login.description")}
</p>
</blockquote> </blockquote>
</motion.div>
</div> </div>
</div> <motion.div
<div className="lg:p-8"> initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ duration: 0.6 }}
className="lg:p-8"
>
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]"> <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center"> <div className="flex flex-col items-center space-y-4 text-center">
<h1 className="text-2xl font-semibold tracking-tight">{t("login.title")}</h1> <div className="relative">
<div className="absolute -inset-1 bg-gradient-to-r from-[#4F46E5] to-[#6366F1] rounded-full opacity-75 blur-md animate-pulse"></div>
<div className="relative bg-gradient-to-br from-white to-gray-100 dark:from-slate-800 dark:to-slate-900 rounded-full shadow-xl overflow-hidden w-24 h-24 flex items-center justify-center">
<motion.img
src="/images/icons/apple-icon-180x180.png"
alt="StripStream Logo"
className="w-[100%] h-[100%] object-cover"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.5 }}
whileHover={{ scale: 1.05 }}
/>
</div>
</div>
<motion.h1
initial={{ y: -20 }}
animate={{ y: 0 }}
className="text-3xl font-bold tracking-tight bg-gradient-to-r from-slate-900 to-slate-700 dark:from-white dark:to-gray-300 bg-clip-text text-transparent"
>
{t("login.title")}
</motion.h1>
<p className="text-sm text-muted-foreground">{t("login.subtitle")}</p> <p className="text-sm text-muted-foreground">{t("login.subtitle")}</p>
</div> </div>
<Tabs defaultValue={defaultTab} className="w-full"> <Tabs defaultValue={defaultTab} className="w-full">
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2 p-1 bg-slate-100 dark:bg-slate-800/50 rounded-lg">
<TabsTrigger value="login">{t("login.tabs.login")}</TabsTrigger> <TabsTrigger
<TabsTrigger value="register">{t("login.tabs.register")}</TabsTrigger> value="login"
className="data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm dark:data-[state=active]:bg-slate-700 transition-all duration-200"
>
{t("login.tabs.login")}
</TabsTrigger>
<TabsTrigger
value="register"
className="data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm dark:data-[state=active]:bg-slate-700 transition-all duration-200"
>
{t("login.tabs.register")}
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="login"> <TabsContent value="login" className="mt-6">
<LoginForm from={searchParams.from} /> <LoginForm from={searchParams.from} />
</TabsContent> </TabsContent>
<TabsContent value="register"> <TabsContent value="register" className="mt-6">
<RegisterForm from={searchParams.from} /> <RegisterForm from={searchParams.from} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>
</div> </motion.div>
</div> </div>
); );
} }

View File

@@ -93,7 +93,7 @@ export function LoginForm({ from }: LoginFormProps) {
<button <button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" className="bg-[#4F46E5] inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
> >
{isLoading ? t("login.form.submit.loading.login") : t("login.form.submit.login")} {isLoading ? t("login.form.submit.loading.login") : t("login.form.submit.login")}
</button> </button>

View File

@@ -104,7 +104,7 @@ export function RegisterForm({ from }: RegisterFormProps) {
<button <button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" className="bg-[#4F46E5] inline-flex w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground ring-offset-background transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
> >
{isLoading ? t("login.form.submit.loading.register") : t("login.form.submit.register")} {isLoading ? t("login.form.submit.loading.register") : t("login.form.submit.register")}
</button> </button>

View File

@@ -2341,6 +2341,15 @@ fraction.js@^4.3.7:
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
framer-motion@12.4.10:
version "12.4.10"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.4.10.tgz#5b2aa0a18f5563edc9603c2c1cbc9d6f19a110a2"
integrity sha512-3Msuyjcr1Pb5hjkn4EJcRe1HumaveP0Gbv4DBMKTPKcV/1GSMkQXj+Uqgneys+9DPcZM18Hac9qY9iUEF5LZtg==
dependencies:
motion-dom "^12.4.10"
motion-utils "^12.4.10"
tslib "^2.4.0"
fs-minipass@^2.0.0: fs-minipass@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@@ -3159,6 +3168,18 @@ mongoose@8.1.0:
ms "2.1.3" ms "2.1.3"
sift "16.0.1" sift "16.0.1"
motion-dom@^12.4.10:
version "12.4.10"
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.4.10.tgz#025886748e33e8ee77742eea7bd088b202e7c83a"
integrity sha512-ISP5u6FTceoD6qKdLupIPU/LyXBrxGox+P2e3mBbm1+pLdlBbwv01YENJr7+1WZnW5ucVKzFScYsV1eXTCG4Xg==
dependencies:
motion-utils "^12.4.10"
motion-utils@^12.4.10:
version "12.4.10"
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.4.10.tgz#3d93acea5454419eaaad8d5e5425cb71cbfa1e7f"
integrity sha512-NPwZd94V013SwRf++jMrk2+HEBgPkeIE2RiOzhAuuQlqxMJPkKt/LXVh6Upl+iN8oarSGD2dlY5/bqgsYXDABA==
mpath@0.9.0: mpath@0.9.0:
version "0.9.0" version "0.9.0"
resolved "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz" resolved "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz"