From 9889131b0370cedcefa9b59b782b1164a8a41589 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 16 Jul 2026 20:06:16 +0200 Subject: [PATCH] Redesign home, login, register pages with magic-hotel dark aesthetic --- src/app/(site)/forgot/page.tsx | 86 ++++---- src/app/(site)/login/page.tsx | 280 +++++++++++++----------- src/app/(site)/register/page.tsx | 2 +- src/components/auth/home-login-form.tsx | 151 +++++++------ src/components/auth/register-form.tsx | 215 ++++++++++-------- src/components/home/guest-view.tsx | 208 ++++++++++++------ src/components/home/user-view.tsx | 99 ++++++--- 7 files changed, 611 insertions(+), 430 deletions(-) diff --git a/src/app/(site)/forgot/page.tsx b/src/app/(site)/forgot/page.tsx index c517184d..edc769fe 100644 --- a/src/app/(site)/forgot/page.tsx +++ b/src/app/(site)/forgot/page.tsx @@ -1,8 +1,7 @@ -import { KeyRound, Lock } from "lucide-react"; +import { KeyRound, Mail } from "lucide-react"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { requestReset } from "@/actions/password-reset"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; export default async function ForgotPage({ searchParams, @@ -13,63 +12,68 @@ export default async function ForgotPage({ const { sent } = await searchParams; return ( -
+
-
-
- -
-

- {t("title")} -

-

- {t("subtitle")} -

-
+
+
+
+ +
+
+
+ +
+

+ {t("title")} +

+

+ {t("subtitle")} +

+
- - - - {sent ? "Email sent" : "Reset your password"} - - - {sent ? t("sentNotice") : "Enter your email to receive a reset link"} - - - {sent ? ( -
-

+

+

{t("sentNotice")}

) : (
-
- - +
+ +
+
+ +
+ +
- )}
-

- +

+ {t("backToLogin")}

- - +
+
); diff --git a/src/app/(site)/login/page.tsx b/src/app/(site)/login/page.tsx index 2eff40e6..27b58d4a 100644 --- a/src/app/(site)/login/page.tsx +++ b/src/app/(site)/login/page.tsx @@ -1,13 +1,11 @@ "use client"; -import { KeyRound, Lock, Mail } from "lucide-react"; +import { Eye, EyeOff, Lock, Mail, User } from "lucide-react"; import Link from "next/link"; import { signIn } from "next-auth/react"; import { useTranslations } from "next-intl"; import { type FormEvent, useState } from "react"; import { precheckLogin } from "@/actions/auth-precheck"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; export default function LoginPage() { const t = useTranslations("pages.login"); @@ -17,6 +15,7 @@ export default function LoginPage() { const [needs2fa, setNeeds2fa] = useState(false); const [error, setError] = useState(null); const [pending, setPending] = useState(false); + const [showPassword, setShowPassword] = useState(false); async function onSubmit(e: FormEvent) { e.preventDefault(); @@ -55,79 +54,107 @@ export default function LoginPage() { return (
-
-
- -
-

- {t("title")} -

-

- {needs2fa ? t("subtitle2fa") : t("subtitle")} -

-
+
+
+
- - - - {needs2fa ? t("subtitle2fa") : t("subtitle")} - - - {needs2fa ? "Enter your authentication code" : "Sign in to your account"} - - - -
-
-
- - setUsername(e.target.value)} - placeholder={t("usernamePlaceholder")} - autoComplete="username" - disabled={needs2fa} - className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] px-10 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20" - /> -
-
- - setPassword(e.target.value)} - placeholder={t("passwordPlaceholder")} - autoComplete="current-password" - disabled={needs2fa} - className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] px-10 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20" - /> -
- {needs2fa ? ( - setCode(e.target.value)} - placeholder={t("codePlaceholder")} - inputMode="numeric" - autoComplete="one-time-code" - autoFocus - className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] px-4 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20" - /> - ) : null} +
+ {/* Header */} +
+
+
+

+ {t("title")} +

+

+ {needs2fa ? t("subtitle2fa") : t("subtitle")} +

+
- {error ? ( -
-

+ + {error && ( +

+

{error}

- ) : null} + )} - +
+
+ {needs2fa && ( +
+ +
+
+ +
+ setCode(e.target.value)} + placeholder={t("codePlaceholder")} + inputMode="numeric" + autoComplete="one-time-code" + autoFocus + className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium" + /> +
+
+ )} +
+ + + + + {/* Divider */} + {!needs2fa && ( + <> +
+
+
+
+
+ + {t("or")} + +
+
+ +
+ + +
+ + )} + +
+

+ {t("noAccount")}{" "} + + {t("createOne")} + + · + + {t("forgotPassword")} + +

+
- - {!needs2fa ? ( - <> -
-
-
-
-
- - {t("or")} - -
-
- -
- - -
- - ) : null} - -
-

- {t("noAccount")}{" "} - - {t("createOne")} - - · - - {t("forgotPassword")} - -

-
- - +
+
); diff --git a/src/app/(site)/register/page.tsx b/src/app/(site)/register/page.tsx index 6ce500c9..834d7b84 100644 --- a/src/app/(site)/register/page.tsx +++ b/src/app/(site)/register/page.tsx @@ -20,7 +20,7 @@ export default async function RegisterPage({ return (
-
+

{t("title")}

-
+
+ {error && ( +
+

+ {error} +

+
+ )} + +
-
-
- - setPassword(e.target.value)} - placeholder="Enter your password" - className="w-full rounded-lg border-2 px-3 py-2.5 text-sm transition-all duration-200 bg-[var(--color-background)]" - style={{ borderColor: "var(--input-border-color)" }} - required - disabled={needs2fa} - /> -
- {needs2fa ? ( -
- +
+
+ + + + +
setCode(e.target.value)} - placeholder="Enter your 2FA code" - inputMode="numeric" - className="w-full rounded-lg border-2 px-3 py-2.5 text-sm transition-all duration-200 bg-[var(--color-background)]" - style={{ borderColor: "var(--input-border-color)" }} - autoFocus + value={username} + onChange={(e) => setUsername(e.target.value)} + placeholder="Username" + className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium" + required + disabled={needs2fa} />
- ) : null} +
+
+ +
+
+ + + + +
+ setPassword(e.target.value)} + placeholder="Password" + className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium" + required + disabled={needs2fa} + /> +
+
+ {needs2fa && ( +
+ +
+
+ + + + +
+ setCode(e.target.value)} + placeholder="Enter your 2FA code" + inputMode="numeric" + autoFocus + className="w-full pl-10 pr-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-[#5AB4C6]/50 focus:bg-[#5AB4C6]/5 text-sm text-white placeholder-gray-500 transition-all font-medium" + /> +
+
+ )}
- {error ? ( -

- {error} -

- ) : null} - + +
+ +
+ + Create account +
+ +
); } diff --git a/src/components/auth/register-form.tsx b/src/components/auth/register-form.tsx index dfe40d33..dd5712e5 100644 --- a/src/components/auth/register-form.tsx +++ b/src/components/auth/register-form.tsx @@ -1,13 +1,10 @@ "use client"; -import { Eye, EyeOff, Lock, Mail, User, UserPlus } from "lucide-react"; -import Image from "next/image"; +import { Eye, EyeOff, Lock, Mail, User } from "lucide-react"; import Link from "next/link"; import { useTranslations } from "next-intl"; import { useActionState, useState } from "react"; import { register } from "@/actions/register"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; interface RegisterFormProps { hotelName: string; @@ -23,100 +20,130 @@ export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) { const [showPassword, setShowPassword] = useState(false); return ( -
-
-
- -
-

- {t("title")} -

-

- {t("subtitle", { hotel: hotelName })} -

-
+
+
+
+
- - - - Create your account - - - Fill in the details below to join {hotelName} - - - -
+
+ {/* Header */} +
+
+ + + + + +
+

+ {t("title")} +

+

+ {t("subtitle", { hotel: hotelName })} +

+
+ + {(error || serverError) && ( -
-

+

+

{error || serverError}

)}
-
- - +
+ +
+
+ +
+ +
-
- - +
+ +
+
+ +
+ +
-
- - - +
+ +
+
+ +
+ + +
-
- - +
+ +
+
+ +
+ +
{/* Terms */} -
+
)} - + -
-

+

+

{t("alreadyHaveAccount")}{" "} - + {t("signIn")}

- - +
+
); } diff --git a/src/components/home/guest-view.tsx b/src/components/home/guest-view.tsx index 6c284743..6d281425 100644 --- a/src/components/home/guest-view.tsx +++ b/src/components/home/guest-view.tsx @@ -1,9 +1,8 @@ -import { ArrowRight, Lock, Newspaper, Sparkles, Users } from "lucide-react"; +import { ArrowRight, Newspaper, Users } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { HomeLoginForm } from "@/components/auth/home-login-form"; -import { Button } from "@/components/ui/button"; import { avatarImageUrl, excerpt } from "@/lib/format"; import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; @@ -41,71 +40,131 @@ export default async function GuestView() { } catch {} return ( -
- {/* Hero Banner */} -
-
-
-
-
-
- - Welcome to {hotelName} +
+ {/* Hero Section */} +
+
+
+
+
+ {/* Left: Welcome */} +
+
+ + + + + + Online +
-

- Join the Community + +

+ Welcome to{" "} + {hotelName}

-

- Connect with friends, explore the hotel, and create unforgettable memories. Your adventure starts here. +

+ Connect with friends, explore the hotel, and create unforgettable + memories. Your adventure starts here.

-
+ +
- +
+ Get Started +
- +
+ + {/* Stats */} +
+
+

+ {articles.length} +

+

+ Articles +

+
+
+

+ {recentUsers.length}+ +

+

+ Active Users +

+
+
-
-
-
-

- + + {/* Right: Login Form */} +
+
+
+
+ + + + +
+ Sign In -

-
-
- +
+
+ {/* Articles + Sidebar */}
{/* Latest News */}
-
+
-
- +
+
-

{t("latestNews")}

+

+ {t("latestNews")} +

- + View all
{articles.length === 0 ? ( -
- -

{t("noArticles")}

+
+ +

{t("noArticles")}

) : (
@@ -113,7 +172,7 @@ export default async function GuestView() {
@@ -124,13 +183,13 @@ export default async function GuestView() { className="object-cover transition-all duration-500 group-hover:scale-110" unoptimized /> -
+
-

+

{a.title}

{a.shortStory && ( -

+

{excerpt(a.shortStory, 80)}

)} @@ -142,7 +201,7 @@ export default async function GuestView() {
-

{a.title}

+

+ {a.title} +

{a.shortStory && ( -

+

{excerpt(a.shortStory, 60)}

)}
- + ))}
@@ -169,13 +230,20 @@ export default async function GuestView() {
{/* Sidebar */} -
+
{/* Recent Users */} {recentUsers.length > 0 && ( -
-
- -

Recent Users

+
+
+
+ +
+

+ Recent Users +

{recentUsers.map((u) => ( @@ -184,7 +252,7 @@ export default async function GuestView() { href={`/u/${u.username}`} className="group relative flex items-center justify-center transition-all duration-200 hover:scale-110" > -
+
- + {u.username} @@ -214,19 +282,35 @@ export default async function GuestView() { {/* Quick Register CTA */} -
-
-
+
+
+
- -

+
+ + + + + +
+

Join {hotelName} Today

-

+

Create your free account and start your adventure!

-
- Register Now +
+ Register Now
diff --git a/src/components/home/user-view.tsx b/src/components/home/user-view.tsx index def89b65..865cec7a 100644 --- a/src/components/home/user-view.tsx +++ b/src/components/home/user-view.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, Gamepad2, Newspaper, Sparkles, Users } from "lucide-react"; +import { ArrowRight, Gamepad2, Newspaper, Users } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; @@ -58,13 +58,13 @@ export default async function UserView({ {/* Main Content */}
{/* Hero/Welcome Banner */} -
-
-
+
+
+
-
+
{username}
-
+
-

- Welcome back, {username} +

+ Welcome back,{" "} + {username}

-

+

{motto || "Happy to see you again!"}

-
- +
+ {t("enterHotel", { hotelName })} - +
-
+
View Profile
@@ -107,18 +111,20 @@ export default async function UserView({
{/* Online Friends Bar */} -
+
-
-
- - +
+
+ +
- {t("onlineFriends")} + + {t("onlineFriends")} +
{onlineFriends.length === 0 ? ( -

+

{t("noFriendsOnline")}

) : ( @@ -128,7 +134,7 @@ export default async function UserView({ href={`/u/${f.username}`} className="group relative transition-all duration-200 hover:scale-110" > -
+
{f.username}
- + {f.username} @@ -153,10 +159,14 @@ export default async function UserView({ {/* Sidebar */}
{latestArticle ? ( -
-
- -

{t("latestArticle")}

+
+
+
+ +
+

+ {t("latestArticle")} +

@@ -167,16 +177,18 @@ export default async function UserView({ className="object-cover transition-transform duration-500 group-hover:scale-110" unoptimized /> -
+
-

{latestArticle.title}

+

+ {latestArticle.title} +

{latestArticle.shortStory && ( -

+

{excerpt(latestArticle.shortStory, 100)}

)} -
+
Read more
@@ -185,13 +197,30 @@ export default async function UserView({ ) : null} {/* Quick Stats */} -
- -

+

+
+ + + + + +
+

Explore the Hotel

-

- Check out the latest news, visit the marketplace, or chat with friends! +

+ Check out the latest news, visit the marketplace, or chat with + friends!