style: login page and logo
This commit is contained in:
7593
package-lock.json
generated
7593
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
21
yarn.lock
21
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user