Redesign home, login, register pages with magic-hotel dark aesthetic
Local Build and Deploy / deploy (push) Successful in 51s
Local Build and Deploy / deploy (push) Successful in 51s
This commit is contained in:
1 parent
d4ee1dc4a3
commit
9889131b03
7 files changed
+611
-430
No files matched your search
@@ -1,8 +1,7 @@
|
|||||||
import { KeyRound, Lock } from "lucide-react";
|
import { KeyRound, Mail } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { requestReset } from "@/actions/password-reset";
|
import { requestReset } from "@/actions/password-reset";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
|
|
||||||
export default async function ForgotPage({
|
export default async function ForgotPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -13,63 +12,68 @@ export default async function ForgotPage({
|
|||||||
const { sent } = await searchParams;
|
const { sent } = await searchParams;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-[60vh] items-center justify-center px-4 py-12">
|
<div className="flex min-h-[70vh] items-center justify-center px-4 py-12">
|
||||||
<div className="w-full max-w-md animate-fade-in">
|
<div className="w-full max-w-md animate-fade-in">
|
||||||
<div className="text-center mb-8">
|
<div className="relative overflow-hidden rounded-3xl border border-white/[0.06] bg-[#09090b] shadow-2xl p-8">
|
||||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5 shadow-lg">
|
<div className="absolute inset-0 bg-gradient-to-br from-[#5AB4C6]/5 via-transparent to-transparent" />
|
||||||
<KeyRound className="h-8 w-8 text-[var(--color-primary)]" />
|
<div className="absolute -top-40 -right-40 w-[400px] h-[400px] bg-[#5AB4C6]/5 rounded-full blur-[120px]" />
|
||||||
</div>
|
|
||||||
<h1 className="text-2xl font-extrabold tracking-tight m-0">
|
<div className="relative">
|
||||||
{t("title")}
|
<div className="text-center mb-8">
|
||||||
</h1>
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-[#5AB4C6]/10 border border-[#5AB4C6]/20">
|
||||||
<p className="mt-1.5 text-sm text-[var(--color-text-muted-readable)]">
|
<KeyRound className="h-6 w-6 text-[#5AB4C6]" />
|
||||||
{t("subtitle")}
|
</div>
|
||||||
</p>
|
<h1 className="text-2xl font-black text-white tracking-tight m-0">
|
||||||
</div>
|
{t("title")}
|
||||||
|
</h1>
|
||||||
|
<p className="mt-1.5 text-sm text-white/40">
|
||||||
|
{t("subtitle")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card className="border-[var(--border-subtle)] shadow-elevated">
|
|
||||||
<CardHeader className="px-6 pt-6 pb-0">
|
|
||||||
<CardTitle className="text-base font-bold">
|
|
||||||
{sent ? "Email sent" : "Reset your password"}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{sent ? t("sentNotice") : "Enter your email to receive a reset link"}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-6">
|
|
||||||
{sent ? (
|
{sent ? (
|
||||||
<div className="rounded-xl bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-900 px-4 py-6 text-center">
|
<div className="rounded-xl bg-green-500/10 border border-green-500/20 px-4 py-6 text-center">
|
||||||
<p className="text-sm font-medium text-green-600 dark:text-green-400 m-0">
|
<p className="text-sm font-bold text-green-400 m-0">
|
||||||
{t("sentNotice")}
|
{t("sentNotice")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form action={requestReset} className="space-y-4">
|
<form action={requestReset} className="space-y-4">
|
||||||
<div className="relative">
|
<div>
|
||||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
<input
|
Email
|
||||||
name="email"
|
</label>
|
||||||
type="email"
|
<div className="relative group">
|
||||||
placeholder={t("emailPlaceholder")}
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
autoComplete="email"
|
<Mail size={15} />
|
||||||
required
|
</div>
|
||||||
className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] pl-10 pr-4 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20"
|
<input
|
||||||
/>
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder={t("emailPlaceholder")}
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="btn btn-primary w-full justify-center py-3 text-sm font-bold rounded-xl">
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full flex items-center justify-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3.5 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20"
|
||||||
|
>
|
||||||
{t("sendResetLink")}
|
{t("sendResetLink")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-sm text-[var(--color-text-muted-readable)]">
|
<p className="text-sm text-white/40">
|
||||||
<Link href="/login" className="font-bold text-[var(--color-primary)] hover:underline">
|
<Link href="/login" className="font-bold text-[#5AB4C6] hover:underline">
|
||||||
{t("backToLogin")}
|
{t("backToLogin")}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+152
-128
@@ -1,13 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { KeyRound, Lock, Mail } from "lucide-react";
|
import { Eye, EyeOff, Lock, Mail, User } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { type FormEvent, useState } from "react";
|
import { type FormEvent, useState } from "react";
|
||||||
import { precheckLogin } from "@/actions/auth-precheck";
|
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() {
|
export default function LoginPage() {
|
||||||
const t = useTranslations("pages.login");
|
const t = useTranslations("pages.login");
|
||||||
@@ -17,6 +15,7 @@ export default function LoginPage() {
|
|||||||
const [needs2fa, setNeeds2fa] = useState(false);
|
const [needs2fa, setNeeds2fa] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
async function onSubmit(e: FormEvent) {
|
async function onSubmit(e: FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -55,79 +54,107 @@ export default function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex min-h-[70vh] items-center justify-center px-4 py-12">
|
<div className="flex min-h-[70vh] items-center justify-center px-4 py-12">
|
||||||
<div className="w-full max-w-md animate-fade-in">
|
<div className="w-full max-w-md animate-fade-in">
|
||||||
<div className="text-center mb-8">
|
<div
|
||||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5 shadow-lg">
|
className="relative overflow-hidden rounded-3xl border border-white/[0.06] bg-[#09090b] shadow-2xl p-8"
|
||||||
<Lock className="h-8 w-8 text-[var(--color-primary)]" />
|
>
|
||||||
</div>
|
<div className="absolute inset-0 bg-gradient-to-br from-[#5AB4C6]/5 via-transparent to-transparent" />
|
||||||
<h1 className="text-2xl font-extrabold tracking-tight m-0">
|
<div className="absolute -top-40 -right-40 w-[400px] h-[400px] bg-[#5AB4C6]/5 rounded-full blur-[120px]" />
|
||||||
{t("title")}
|
|
||||||
</h1>
|
|
||||||
<p className="mt-1.5 text-sm text-[var(--color-text-muted-readable)]">
|
|
||||||
{needs2fa ? t("subtitle2fa") : t("subtitle")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="border-[var(--border-subtle)] shadow-elevated">
|
<div className="relative">
|
||||||
<CardHeader className="px-6 pt-6 pb-0">
|
{/* Header */}
|
||||||
<CardTitle className="text-base font-bold">
|
<div className="text-center mb-8">
|
||||||
{needs2fa ? t("subtitle2fa") : t("subtitle")}
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-[#5AB4C6]/10 border border-[#5AB4C6]/20">
|
||||||
</CardTitle>
|
<Lock className="h-6 w-6 text-[#5AB4C6]" />
|
||||||
<CardDescription>
|
|
||||||
{needs2fa ? "Enter your authentication code" : "Sign in to your account"}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<form onSubmit={onSubmit} className="space-y-4">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="relative">
|
|
||||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
|
||||||
<input
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
|
||||||
<KeyRound className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{needs2fa ? (
|
|
||||||
<input
|
|
||||||
value={code}
|
|
||||||
onChange={(e) => 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}
|
|
||||||
</div>
|
</div>
|
||||||
|
<h1 className="text-2xl font-black text-white tracking-tight m-0">
|
||||||
|
{t("title")}
|
||||||
|
</h1>
|
||||||
|
<p className="mt-1.5 text-sm text-white/40">
|
||||||
|
{needs2fa ? t("subtitle2fa") : t("subtitle")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{error ? (
|
<form onSubmit={onSubmit} className="space-y-4">
|
||||||
<div className="rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900 px-4 py-3">
|
{error && (
|
||||||
<p className="text-sm font-medium text-red-600 dark:text-red-400 text-center m-0">
|
<div className="rounded-xl bg-red-500/10 border border-red-500/20 px-4 py-3">
|
||||||
|
<p className="text-xs font-bold text-red-400 text-center m-0">
|
||||||
{error}
|
{error}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
)}
|
||||||
|
|
||||||
<Button
|
<div className="space-y-3.5">
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
|
<User size={15} />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
placeholder={t("usernamePlaceholder")}
|
||||||
|
autoComplete="username"
|
||||||
|
disabled={needs2fa}
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
|
<Lock size={15} />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
placeholder={t("passwordPlaceholder")}
|
||||||
|
autoComplete="current-password"
|
||||||
|
disabled={needs2fa}
|
||||||
|
className="w-full pl-10 pr-10 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff size={15} /> : <Eye size={15} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{needs2fa && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
|
2FA Code
|
||||||
|
</label>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
|
<Mail size={15} />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
size="lg"
|
className="w-full flex items-center justify-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3.5 rounded-lg transition-all disabled:opacity-50 active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20"
|
||||||
className="w-full font-bold text-base h-12 rounded-xl"
|
|
||||||
>
|
>
|
||||||
{pending ? (
|
{pending ? (
|
||||||
<span className="flex items-center justify-center gap-2">
|
<span className="flex items-center justify-center gap-2">
|
||||||
@@ -138,68 +165,65 @@ export default function LoginPage() {
|
|||||||
{t("pleaseWait")}
|
{t("pleaseWait")}
|
||||||
</span>
|
</span>
|
||||||
) : needs2fa ? t("verify") : t("signIn")}
|
) : needs2fa ? t("verify") : t("signIn")}
|
||||||
</Button>
|
</button>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
{!needs2fa && (
|
||||||
|
<>
|
||||||
|
<div className="relative my-4">
|
||||||
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<div className="w-full border-t border-white/[0.06]" />
|
||||||
|
</div>
|
||||||
|
<div className="relative flex justify-center text-[10px] uppercase tracking-widest font-black">
|
||||||
|
<span className="bg-[#09090b] px-3 text-white/30">
|
||||||
|
{t("or")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => signIn("discord", { callbackUrl: "/" })}
|
||||||
|
className="w-full flex items-center justify-center gap-2 bg-white/5 hover:bg-white/10 text-white font-black text-xs py-3 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest border border-white/10"
|
||||||
|
>
|
||||||
|
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z" />
|
||||||
|
</svg>
|
||||||
|
{t("continueWithDiscord")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => signIn("google", { callbackUrl: "/" })}
|
||||||
|
className="w-full flex items-center justify-center gap-2 bg-white/5 hover:bg-white/10 text-white font-black text-xs py-3 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest border border-white/10"
|
||||||
|
>
|
||||||
|
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4" />
|
||||||
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
|
||||||
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
|
||||||
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
|
||||||
|
</svg>
|
||||||
|
{t("continueWithGoogle")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="pt-2 text-center">
|
||||||
|
<p className="text-sm text-white/40">
|
||||||
|
{t("noAccount")}{" "}
|
||||||
|
<Link href="/register" className="font-bold text-[#5AB4C6] hover:underline">
|
||||||
|
{t("createOne")}
|
||||||
|
</Link>
|
||||||
|
<span className="mx-2 text-white/10">·</span>
|
||||||
|
<Link href="/forgot" className="font-bold text-[#5AB4C6] hover:underline">
|
||||||
|
{t("forgotPassword")}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{!needs2fa ? (
|
</div>
|
||||||
<>
|
|
||||||
<div className="relative my-6">
|
|
||||||
<div className="absolute inset-0 flex items-center">
|
|
||||||
<div className="w-full border-t border-[var(--border-subtle)]" />
|
|
||||||
</div>
|
|
||||||
<div className="relative flex justify-center text-xs uppercase">
|
|
||||||
<span className="bg-[var(--color-card)] px-3 text-[var(--color-text-muted-readable)] font-bold">
|
|
||||||
{t("or")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-3">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="lg"
|
|
||||||
className="w-full font-semibold h-11 rounded-xl"
|
|
||||||
onClick={() => signIn("discord", { callbackUrl: "/" })}
|
|
||||||
>
|
|
||||||
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189z" />
|
|
||||||
</svg>
|
|
||||||
{t("continueWithDiscord")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
size="lg"
|
|
||||||
className="w-full font-semibold h-11 rounded-xl"
|
|
||||||
onClick={() => signIn("google", { callbackUrl: "/" })}
|
|
||||||
>
|
|
||||||
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4" />
|
|
||||||
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
|
|
||||||
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
|
|
||||||
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
|
|
||||||
</svg>
|
|
||||||
{t("continueWithGoogle")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
|
||||||
<p className="text-sm text-[var(--color-text-muted-readable)]">
|
|
||||||
{t("noAccount")}{" "}
|
|
||||||
<Link href="/register" className="font-bold text-[var(--color-primary)] hover:underline">
|
|
||||||
{t("createOne")}
|
|
||||||
</Link>
|
|
||||||
<span className="mx-2 text-[var(--border-subtle)]">·</span>
|
|
||||||
<Link href="/forgot" className="font-bold text-[var(--color-primary)] hover:underline">
|
|
||||||
{t("forgotPassword")}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default async function RegisterPage({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-[calc(100vh-200px)] items-center justify-center px-4 py-8">
|
<main className="flex min-h-[calc(100vh-200px)] items-center justify-center px-4 py-8">
|
||||||
<div className="w-full">
|
<div className="w-full max-w-lg">
|
||||||
<h1 className="sr-only">{t("title")}</h1>
|
<h1 className="sr-only">{t("title")}</h1>
|
||||||
<RegisterForm
|
<RegisterForm
|
||||||
hotelName={hotelName ?? "Atom"}
|
hotelName={hotelName ?? "Atom"}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { LogIn, UserPlus } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { type FormEvent, useState } from "react";
|
import { type FormEvent, useState } from "react";
|
||||||
import { precheckLogin } from "@/actions/auth-precheck";
|
import { precheckLogin } from "@/actions/auth-precheck";
|
||||||
@@ -47,86 +49,103 @@ export function HomeLoginForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} className="flex flex-col gap-4">
|
<form onSubmit={onSubmit} className="space-y-4">
|
||||||
<div className="space-y-3">
|
{error && (
|
||||||
|
<div className="rounded-lg bg-red-500/10 border border-red-500/20 px-4 py-2.5">
|
||||||
|
<p className="text-xs font-bold text-red-400 text-center m-0">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-3.5">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-bold uppercase tracking-wider text-[var(--color-text-muted-readable)] mb-1.5">
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
Username
|
Username
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div className="relative group">
|
||||||
type="text"
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
value={username}
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
|
||||||
placeholder="Enter your username"
|
<circle cx="12" cy="7" r="4" />
|
||||||
className="w-full rounded-lg border-2 px-3 py-2.5 text-sm transition-all duration-200 bg-[var(--color-background)]"
|
</svg>
|
||||||
style={{ borderColor: "var(--input-border-color)" }}
|
</div>
|
||||||
required
|
|
||||||
disabled={needs2fa}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-bold uppercase tracking-wider text-[var(--color-text-muted-readable)] mb-1.5">
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => 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}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{needs2fa ? (
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-bold uppercase tracking-wider text-[var(--color-text-muted-readable)] mb-1.5">
|
|
||||||
2FA Code
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={code}
|
value={username}
|
||||||
onChange={(e) => setCode(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
placeholder="Enter your 2FA code"
|
placeholder="Username"
|
||||||
inputMode="numeric"
|
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"
|
||||||
className="w-full rounded-lg border-2 px-3 py-2.5 text-sm transition-all duration-200 bg-[var(--color-background)]"
|
required
|
||||||
style={{ borderColor: "var(--input-border-color)" }}
|
disabled={needs2fa}
|
||||||
autoFocus
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => 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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{needs2fa && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
|
2FA Code
|
||||||
|
</label>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error ? (
|
|
||||||
<p className="m-0 text-center text-sm font-medium text-red-500 dark:text-red-400">
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={pending}
|
disabled={pending}
|
||||||
className="w-full cursor-pointer rounded-xl px-4 py-3 text-sm font-bold tracking-wide text-white transition-all duration-200 hover:opacity-90 active:scale-[0.98] disabled:opacity-50"
|
className="w-full group relative flex items-center justify-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3.5 rounded-lg transition-all disabled:opacity-50 active:scale-[0.98] uppercase tracking-widest"
|
||||||
style={{
|
|
||||||
background: "linear-gradient(135deg, var(--button-color), color-mix(in srgb, var(--button-color) 80%, #000000))",
|
|
||||||
boxShadow: "0 4px 16px color-mix(in srgb, var(--button-color) 30%, transparent)",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{pending ? (
|
<LogIn size={14} />
|
||||||
<span className="flex items-center justify-center gap-2">
|
{pending ? "Please wait..." : needs2fa ? "Verify" : "Login"}
|
||||||
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
|
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
||||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
|
||||||
</svg>
|
|
||||||
Please wait...
|
|
||||||
</span>
|
|
||||||
) : needs2fa ? (
|
|
||||||
"Verify"
|
|
||||||
) : (
|
|
||||||
"Login"
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div className="pt-4 border-t border-white/5 text-center">
|
||||||
|
<Link href="/register">
|
||||||
|
<div className="flex items-center justify-center gap-2 w-full py-3 rounded-lg bg-white/5 border border-white/10 text-white text-[11px] font-black hover:bg-white/10 transition-all active:scale-95 uppercase tracking-wider">
|
||||||
|
<UserPlus size={14} className="text-[#5AB4C6]" />
|
||||||
|
Create account
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Eye, EyeOff, Lock, Mail, User, UserPlus } from "lucide-react";
|
import { Eye, EyeOff, Lock, Mail, User } from "lucide-react";
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useActionState, useState } from "react";
|
import { useActionState, useState } from "react";
|
||||||
import { register } from "@/actions/register";
|
import { register } from "@/actions/register";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
|
|
||||||
interface RegisterFormProps {
|
interface RegisterFormProps {
|
||||||
hotelName: string;
|
hotelName: string;
|
||||||
@@ -23,100 +20,130 @@ export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-lg animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
<div className="text-center mb-8">
|
<div
|
||||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5 shadow-lg">
|
className="relative overflow-hidden rounded-3xl border border-white/[0.06] bg-[#09090b] shadow-2xl p-8 md:p-10"
|
||||||
<UserPlus className="h-8 w-8 text-[var(--color-primary)]" />
|
>
|
||||||
</div>
|
<div className="absolute inset-0 bg-gradient-to-br from-[#5AB4C6]/5 via-transparent to-transparent" />
|
||||||
<h1 className="text-2xl font-extrabold tracking-tight m-0">
|
<div className="absolute -top-40 -right-40 w-[500px] h-[500px] bg-[#5AB4C6]/5 rounded-full blur-[120px]" />
|
||||||
{t("title")}
|
|
||||||
</h1>
|
|
||||||
<p className="mt-1.5 text-sm text-[var(--color-text-muted-readable)]">
|
|
||||||
{t("subtitle", { hotel: hotelName })}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="border-[var(--border-subtle)] shadow-elevated">
|
<div className="relative">
|
||||||
<CardHeader className="px-6 pt-6 pb-0">
|
{/* Header */}
|
||||||
<CardTitle className="text-base font-bold">
|
<div className="text-center mb-8">
|
||||||
Create your account
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-[#5AB4C6]/10 border border-[#5AB4C6]/20">
|
||||||
</CardTitle>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#5AB4C6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<CardDescription>
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||||
Fill in the details below to join {hotelName}
|
<polyline points="10 17 15 12 10 7" />
|
||||||
</CardDescription>
|
<line x1="15" x2="3" y1="12" y2="12" />
|
||||||
</CardHeader>
|
</svg>
|
||||||
<CardContent className="p-6">
|
</div>
|
||||||
<form action={formAction} className="space-y-5">
|
<h1 className="text-2xl font-black text-white tracking-tight m-0">
|
||||||
|
{t("title")}
|
||||||
|
</h1>
|
||||||
|
<p className="mt-1.5 text-sm text-white/40">
|
||||||
|
{t("subtitle", { hotel: hotelName })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action={formAction} className="space-y-5 max-w-md mx-auto">
|
||||||
{(error || serverError) && (
|
{(error || serverError) && (
|
||||||
<div className="rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900 px-4 py-3">
|
<div className="rounded-xl bg-red-500/10 border border-red-500/20 px-4 py-3">
|
||||||
<p className="text-sm font-medium text-red-600 dark:text-red-400 text-center m-0">
|
<p className="text-xs font-bold text-red-400 text-center m-0">
|
||||||
{error || serverError}
|
{error || serverError}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
<div className="relative">
|
<div>
|
||||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
<input
|
Username
|
||||||
id="username"
|
</label>
|
||||||
name="username"
|
<div className="relative group">
|
||||||
type="text"
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
placeholder={t("usernamePlaceholder")}
|
<User size={15} />
|
||||||
autoComplete="username"
|
</div>
|
||||||
className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] pl-10 pr-4 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20"
|
<input
|
||||||
required
|
id="username"
|
||||||
/>
|
name="username"
|
||||||
|
type="text"
|
||||||
|
placeholder={t("usernamePlaceholder")}
|
||||||
|
autoComplete="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
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div>
|
||||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
<input
|
Email
|
||||||
id="mail"
|
</label>
|
||||||
name="mail"
|
<div className="relative group">
|
||||||
type="email"
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
placeholder={t("emailPlaceholder")}
|
<Mail size={15} />
|
||||||
autoComplete="email"
|
</div>
|
||||||
className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] pl-10 pr-4 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20"
|
<input
|
||||||
/>
|
id="mail"
|
||||||
|
name="mail"
|
||||||
|
type="email"
|
||||||
|
placeholder={t("emailPlaceholder")}
|
||||||
|
autoComplete="email"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
<div className="relative">
|
<div>
|
||||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
<input
|
Password
|
||||||
id="password"
|
</label>
|
||||||
name="password"
|
<div className="relative group">
|
||||||
type={showPassword ? "text" : "password"}
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
placeholder={t("passwordPlaceholder")}
|
<Lock size={15} />
|
||||||
autoComplete="new-password"
|
</div>
|
||||||
className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] pl-10 pr-10 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20"
|
<input
|
||||||
required
|
id="password"
|
||||||
/>
|
name="password"
|
||||||
<button
|
type={showPassword ? "text" : "password"}
|
||||||
type="button"
|
placeholder={t("passwordPlaceholder")}
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
autoComplete="new-password"
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-readable)]"
|
className="w-full pl-10 pr-10 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
|
||||||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
/>
|
||||||
</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff size={15} /> : <Eye size={15} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div>
|
||||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--color-text-muted)]" />
|
<label className="block text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1.5 ml-1">
|
||||||
<input
|
Confirm
|
||||||
id="password_confirmation"
|
</label>
|
||||||
name="password_confirmation"
|
<div className="relative group">
|
||||||
type="password"
|
<div className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-[#5AB4C6] transition-colors">
|
||||||
placeholder={t("confirmPasswordPlaceholder")}
|
<Lock size={15} />
|
||||||
autoComplete="new-password"
|
</div>
|
||||||
className="w-full rounded-xl border-2 border-[var(--input-border-color)] bg-[var(--color-background)] pl-10 pr-4 py-2.5 text-sm transition-all duration-200 focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/20"
|
<input
|
||||||
required
|
id="password_confirmation"
|
||||||
/>
|
name="password_confirmation"
|
||||||
|
type="password"
|
||||||
|
placeholder={t("confirmPasswordPlaceholder")}
|
||||||
|
autoComplete="new-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
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Terms */}
|
{/* Terms */}
|
||||||
<div className="flex items-start gap-3 rounded-xl border border-[var(--border-subtle)] bg-[var(--color-background)] p-4">
|
<div className="flex items-start gap-3 rounded-xl border border-white/10 bg-white/[0.03] p-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
@@ -124,12 +151,8 @@ export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) {
|
|||||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||||
className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-md border-2 transition-all"
|
className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-md border-2 transition-all"
|
||||||
style={{
|
style={{
|
||||||
borderColor: termsAccepted
|
borderColor: termsAccepted ? "#5AB4C6" : "rgba(255,255,255,0.15)",
|
||||||
? "var(--color-primary)"
|
backgroundColor: termsAccepted ? "#5AB4C6" : "transparent",
|
||||||
: "var(--input-border-color)",
|
|
||||||
backgroundColor: termsAccepted
|
|
||||||
? "var(--color-primary)"
|
|
||||||
: "transparent",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{termsAccepted && (
|
{termsAccepted && (
|
||||||
@@ -140,8 +163,7 @@ export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) {
|
|||||||
</button>
|
</button>
|
||||||
<input type="hidden" name="terms" value={termsAccepted ? "1" : ""} />
|
<input type="hidden" name="terms" value={termsAccepted ? "1" : ""} />
|
||||||
<label
|
<label
|
||||||
className="text-sm cursor-pointer select-none"
|
className="text-sm cursor-pointer select-none text-white/60"
|
||||||
style={{ color: "var(--color-text-readable)" }}
|
|
||||||
onClick={() => setTermsAccepted(!termsAccepted)}
|
onClick={() => setTermsAccepted(!termsAccepted)}
|
||||||
>
|
>
|
||||||
{t("termsAccept", { hotel: hotelName })}
|
{t("termsAccept", { hotel: hotelName })}
|
||||||
@@ -160,11 +182,10 @@ export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
size="lg"
|
className="w-full flex items-center justify-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3.5 rounded-lg transition-all disabled:opacity-50 active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20"
|
||||||
className="w-full font-bold text-base h-12 rounded-xl"
|
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<span className="flex items-center justify-center gap-2">
|
<span className="flex items-center justify-center gap-2">
|
||||||
@@ -175,19 +196,19 @@ export function RegisterForm({ hotelName, captcha, error }: RegisterFormProps) {
|
|||||||
{t("creatingAccount")}
|
{t("creatingAccount")}
|
||||||
</span>
|
</span>
|
||||||
) : t("createAccount")}
|
) : t("createAccount")}
|
||||||
</Button>
|
</button>
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center pt-2">
|
||||||
<p className="text-sm text-[var(--color-text-muted-readable)]">
|
<p className="text-sm text-white/40">
|
||||||
{t("alreadyHaveAccount")}{" "}
|
{t("alreadyHaveAccount")}{" "}
|
||||||
<Link href="/login" className="font-bold text-[var(--color-primary)] hover:underline">
|
<Link href="/login" className="font-bold text-[#5AB4C6] hover:underline">
|
||||||
{t("signIn")}
|
{t("signIn")}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { HomeLoginForm } from "@/components/auth/home-login-form";
|
import { HomeLoginForm } from "@/components/auth/home-login-form";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { avatarImageUrl, excerpt } from "@/lib/format";
|
import { avatarImageUrl, excerpt } from "@/lib/format";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
@@ -41,71 +40,131 @@ export default async function GuestView() {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-10">
|
||||||
{/* Hero Banner */}
|
{/* Hero Section */}
|
||||||
<div className="animate-fade-in relative overflow-hidden rounded-2xl border border-[var(--border-subtle)] bg-gradient-to-br from-[var(--color-primary)]/10 via-[var(--color-surface)] to-[var(--color-accent)]/5 shadow-elevated">
|
<div className="relative overflow-hidden rounded-3xl bg-[#09090b] border border-white/[0.06] shadow-2xl">
|
||||||
<div className="absolute -top-20 -right-20 h-64 w-64 rounded-full opacity-[0.08] blur-[100px]" style={{ background: "var(--color-primary)" }} />
|
<div className="absolute inset-0 bg-gradient-to-br from-[#5AB4C6]/5 via-transparent to-transparent" />
|
||||||
<div className="absolute -bottom-20 -left-20 h-48 w-48 rounded-full opacity-[0.06] blur-[80px]" style={{ background: "var(--color-accent)" }} />
|
<div className="absolute top-0 left-1/4 w-[600px] h-[600px] bg-[#5AB4C6]/5 rounded-full blur-[120px]" />
|
||||||
<div className="relative flex flex-col items-center gap-6 px-8 py-10 text-center md:flex-row md:text-left">
|
<div className="absolute -bottom-20 -right-20 w-80 h-80 bg-white/[0.03] rounded-full blur-[80px]" />
|
||||||
<div className="flex-1">
|
<div className="relative flex flex-col items-center gap-8 px-6 py-10 md:flex-row md:px-10 md:py-12">
|
||||||
<div className="mb-2 inline-flex items-center gap-1.5 rounded-full border border-[var(--border-subtle)] bg-[var(--color-surface)] px-3 py-1 text-xs font-bold uppercase tracking-wider text-[var(--color-text-muted-readable)]">
|
{/* Left: Welcome */}
|
||||||
<Sparkles className="h-3 w-3 text-[var(--color-primary)]" />
|
<div className="flex-1 text-center md:text-left">
|
||||||
Welcome to {hotelName}
|
<div className="inline-flex items-center gap-2 rounded-full border border-[#5AB4C6]/20 bg-[#5AB4C6]/10 px-4 py-1.5 mb-5">
|
||||||
|
<span className="relative flex h-2 w-2">
|
||||||
|
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#5AB4C6] opacity-80" />
|
||||||
|
<span className="relative inline-flex h-2 w-2 rounded-full bg-[#5AB4C6]" />
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] font-black uppercase tracking-widest text-[#5AB4C6]">
|
||||||
|
Online
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="m-0 text-3xl font-extrabold tracking-tight md:text-4xl">
|
|
||||||
Join the <span className="text-[var(--color-primary)]">Community</span>
|
<h1 className="m-0 text-3xl font-black tracking-tight text-white md:text-4xl">
|
||||||
|
Welcome to{" "}
|
||||||
|
<span className="text-[#5AB4C6]">{hotelName}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 max-w-md text-sm text-[var(--color-text-muted-readable)]">
|
<p className="mt-3 max-w-md text-sm text-white/50 leading-relaxed">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-5 flex flex-wrap gap-3">
|
|
||||||
|
<div className="mt-7 flex flex-wrap gap-3">
|
||||||
<Link href="/register">
|
<Link href="/register">
|
||||||
<Button size="lg" className="font-bold gap-2 rounded-xl px-8 h-12 shadow-glow">
|
<div className="inline-flex items-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3.5 px-7 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20">
|
||||||
Get Started <ArrowRight className="h-4 w-4" />
|
Get Started <ArrowRight size={14} />
|
||||||
</Button>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/news">
|
<Link href="/news">
|
||||||
<Button variant="outline" size="lg" className="font-bold rounded-xl px-8 h-12">
|
<div className="inline-flex items-center gap-2 bg-white/5 hover:bg-white/10 text-white font-black text-xs py-3.5 px-7 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest border border-white/10">
|
||||||
Latest News
|
Latest News
|
||||||
</Button>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="mt-8 flex gap-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-black text-white m-0">
|
||||||
|
{articles.length}
|
||||||
|
</p>
|
||||||
|
<p className="text-[10px] font-black uppercase tracking-widest text-white/30 m-0 mt-0.5">
|
||||||
|
Articles
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-black text-white m-0">
|
||||||
|
{recentUsers.length}+
|
||||||
|
</p>
|
||||||
|
<p className="text-[10px] font-black uppercase tracking-widest text-white/30 m-0 mt-0.5">
|
||||||
|
Active Users
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0">
|
|
||||||
<div className="w-full max-w-xs rounded-2xl border border-[var(--border-subtle)] bg-[var(--color-surface)] shadow-elevated md:w-72">
|
{/* Right: Login Form */}
|
||||||
<div className="rounded-t-2xl bg-gradient-to-r from-[var(--color-primary)]/10 to-[var(--color-primary)]/5 px-5 py-3 border-b border-[var(--border-subtle)]">
|
<div className="w-full max-w-sm shrink-0">
|
||||||
<h3 className="m-0 text-sm font-bold flex items-center gap-2">
|
<div
|
||||||
<Lock className="h-4 w-4 text-[var(--color-primary)]" />
|
className="rounded-2xl border border-white/10 p-6"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"linear-gradient(135deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.02) 100%)",
|
||||||
|
backdropFilter: "blur(32px)",
|
||||||
|
WebkitBackdropFilter: "blur(32px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2.5 mb-5">
|
||||||
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-[#5AB4C6]/10">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#5AB4C6"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-black text-white tracking-wide">
|
||||||
Sign In
|
Sign In
|
||||||
</h3>
|
</span>
|
||||||
</div>
|
|
||||||
<div className="p-4">
|
|
||||||
<HomeLoginForm />
|
|
||||||
</div>
|
</div>
|
||||||
|
<HomeLoginForm />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Articles + Sidebar */}
|
||||||
<div className="grid gap-6 lg:grid-cols-3">
|
<div className="grid gap-6 lg:grid-cols-3">
|
||||||
{/* Latest News */}
|
{/* Latest News */}
|
||||||
<div className="lg:col-span-2 animate-slide-up">
|
<div className="lg:col-span-2 animate-slide-up">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-5 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5">
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-[#5AB4C6]/10">
|
||||||
<Newspaper className="h-4 w-4 text-[var(--color-primary)]" />
|
<Newspaper className="h-4 w-4 text-[#5AB4C6]" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="m-0 text-lg font-extrabold">{t("latestNews")}</h2>
|
<h2 className="m-0 text-sm font-black text-white uppercase tracking-wider">
|
||||||
|
{t("latestNews")}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/news" className="text-xs font-bold text-[var(--color-primary)] hover:underline flex items-center gap-1">
|
<Link
|
||||||
|
href="/news"
|
||||||
|
className="text-[10px] font-black uppercase tracking-widest text-[#5AB4C6] hover:underline flex items-center gap-1"
|
||||||
|
>
|
||||||
View all <ArrowRight className="h-3 w-3" />
|
View all <ArrowRight className="h-3 w-3" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{articles.length === 0 ? (
|
{articles.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center rounded-2xl border border-dashed border-[var(--border-subtle)] bg-[var(--color-surface)] px-6 py-16 text-center">
|
<div className="flex flex-col items-center justify-center rounded-2xl border border-dashed border-white/10 bg-white/[0.03] px-6 py-16 text-center">
|
||||||
<Newspaper className="mb-3 h-10 w-10 text-[var(--color-text-muted)] opacity-40" />
|
<Newspaper className="mb-3 h-10 w-10 text-white/20" />
|
||||||
<p className="text-sm text-[var(--color-text-muted-readable)] m-0">{t("noArticles")}</p>
|
<p className="text-sm text-white/40 m-0">{t("noArticles")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
@@ -113,7 +172,7 @@ export default async function GuestView() {
|
|||||||
<Link
|
<Link
|
||||||
key={a.slug}
|
key={a.slug}
|
||||||
href={`/news/${a.slug}`}
|
href={`/news/${a.slug}`}
|
||||||
className="group relative overflow-hidden rounded-2xl border border-[var(--border-subtle)] bg-[var(--color-surface)] transition-all duration-300 hover:shadow-card-hover hover:-translate-y-1"
|
className="group relative overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] transition-all duration-300 hover:bg-white/[0.06] hover:-translate-y-1"
|
||||||
style={{ animationDelay: `${i * 100}ms` }}
|
style={{ animationDelay: `${i * 100}ms` }}
|
||||||
>
|
>
|
||||||
<div className="relative aspect-[16/9] overflow-hidden">
|
<div className="relative aspect-[16/9] overflow-hidden">
|
||||||
@@ -124,13 +183,13 @@ export default async function GuestView() {
|
|||||||
className="object-cover transition-all duration-500 group-hover:scale-110"
|
className="object-cover transition-all duration-500 group-hover:scale-110"
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-[#09090b] via-black/20 to-transparent" />
|
||||||
<div className="absolute bottom-0 left-0 right-0 p-4">
|
<div className="absolute bottom-0 left-0 right-0 p-4">
|
||||||
<h3 className="m-0 text-base font-bold text-white text-shadow-sm truncate">
|
<h3 className="m-0 text-base font-bold text-white truncate">
|
||||||
{a.title}
|
{a.title}
|
||||||
</h3>
|
</h3>
|
||||||
{a.shortStory && (
|
{a.shortStory && (
|
||||||
<p className="mt-1 truncate text-xs text-white/80 text-shadow-sm">
|
<p className="mt-1 truncate text-xs text-white/60">
|
||||||
{excerpt(a.shortStory, 80)}
|
{excerpt(a.shortStory, 80)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -142,7 +201,7 @@ export default async function GuestView() {
|
|||||||
<Link
|
<Link
|
||||||
key={a.slug}
|
key={a.slug}
|
||||||
href={`/news/${a.slug}`}
|
href={`/news/${a.slug}`}
|
||||||
className="group flex items-center gap-3 rounded-xl border border-[var(--border-subtle)] bg-[var(--color-surface)] p-3 transition-all duration-200 hover:shadow-card-hover hover:-translate-y-0.5"
|
className="group flex items-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] p-3 transition-all duration-200 hover:bg-white/[0.06] hover:-translate-y-0.5"
|
||||||
>
|
>
|
||||||
<div className="relative h-16 w-16 shrink-0 overflow-hidden rounded-lg">
|
<div className="relative h-16 w-16 shrink-0 overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
@@ -154,14 +213,16 @@ export default async function GuestView() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h4 className="m-0 text-sm font-bold truncate">{a.title}</h4>
|
<h4 className="m-0 text-sm font-bold text-white truncate">
|
||||||
|
{a.title}
|
||||||
|
</h4>
|
||||||
{a.shortStory && (
|
{a.shortStory && (
|
||||||
<p className="m-0 mt-0.5 truncate text-xs text-[var(--color-text-muted-readable)]">
|
<p className="m-0 mt-0.5 truncate text-xs text-white/40">
|
||||||
{excerpt(a.shortStory, 60)}
|
{excerpt(a.shortStory, 60)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight className="h-4 w-4 shrink-0 text-[var(--color-text-muted)] opacity-0 -translate-x-2 transition-all duration-200 group-hover:opacity-100 group-hover:translate-x-0" />
|
<ArrowRight className="h-4 w-4 shrink-0 text-white/20 opacity-0 -translate-x-2 transition-all duration-200 group-hover:opacity-100 group-hover:translate-x-0" />
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -169,13 +230,20 @@ export default async function GuestView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="animate-slide-up space-y-6" style={{ animationDelay: "150ms" }}>
|
<div
|
||||||
|
className="animate-slide-up space-y-6"
|
||||||
|
style={{ animationDelay: "150ms" }}
|
||||||
|
>
|
||||||
{/* Recent Users */}
|
{/* Recent Users */}
|
||||||
{recentUsers.length > 0 && (
|
{recentUsers.length > 0 && (
|
||||||
<div className="rounded-2xl border border-[var(--border-subtle)] bg-[var(--color-surface)] overflow-hidden">
|
<div className="rounded-2xl border border-white/10 bg-white/[0.03] overflow-hidden">
|
||||||
<div className="flex items-center gap-2.5 bg-gradient-to-r from-[var(--color-primary)]/10 to-[var(--color-primary)]/5 px-5 py-3 border-b border-[var(--border-subtle)]">
|
<div className="flex items-center gap-2.5 px-5 py-3.5 border-b border-white/[0.06]">
|
||||||
<Users className="h-4 w-4 text-[var(--color-primary)]" />
|
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-[#5AB4C6]/10">
|
||||||
<h3 className="m-0 text-sm font-bold">Recent Users</h3>
|
<Users className="h-3.5 w-3.5 text-[#5AB4C6]" />
|
||||||
|
</div>
|
||||||
|
<h3 className="m-0 text-[11px] font-black text-white uppercase tracking-wider">
|
||||||
|
Recent Users
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 gap-2 p-4">
|
<div className="grid grid-cols-4 gap-2 p-4">
|
||||||
{recentUsers.map((u) => (
|
{recentUsers.map((u) => (
|
||||||
@@ -184,7 +252,7 @@ export default async function GuestView() {
|
|||||||
href={`/u/${u.username}`}
|
href={`/u/${u.username}`}
|
||||||
className="group relative flex items-center justify-center transition-all duration-200 hover:scale-110"
|
className="group relative flex items-center justify-center transition-all duration-200 hover:scale-110"
|
||||||
>
|
>
|
||||||
<div className="relative h-14 w-14 overflow-hidden rounded-xl border-2 border-transparent bg-[var(--color-background)] transition-all duration-200 group-hover:border-[var(--color-primary)]/30 group-hover:shadow-lg">
|
<div className="relative h-14 w-14 overflow-hidden rounded-xl border-2 border-transparent bg-white/5 transition-all duration-200 group-hover:border-[#5AB4C6]/30 group-hover:shadow-lg">
|
||||||
<Image
|
<Image
|
||||||
src={avatarImageUrl(imager, u.look, {
|
src={avatarImageUrl(imager, u.look, {
|
||||||
headOnly: true,
|
headOnly: true,
|
||||||
@@ -203,7 +271,7 @@ export default async function GuestView() {
|
|||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-[var(--color-surface)] px-2 py-0.5 text-[0.55rem] font-bold text-[var(--color-text-muted)] opacity-0 shadow-lg transition-all duration-200 group-hover:opacity-100">
|
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-[#09090b] px-2 py-0.5 text-[0.55rem] font-bold text-white/60 opacity-0 shadow-lg transition-all duration-200 group-hover:opacity-100 border border-white/10">
|
||||||
{u.username}
|
{u.username}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -214,19 +282,35 @@ export default async function GuestView() {
|
|||||||
|
|
||||||
{/* Quick Register CTA */}
|
{/* Quick Register CTA */}
|
||||||
<Link href="/register" className="group block">
|
<Link href="/register" className="group block">
|
||||||
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary)]/80 px-6 py-8 text-center transition-all duration-300 hover:shadow-glow hover:-translate-y-1">
|
<div className="relative overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03] px-6 py-8 text-center transition-all duration-300 hover:bg-white/[0.06] hover:-translate-y-1">
|
||||||
<div className="absolute -top-10 -right-10 h-32 w-32 rounded-full bg-white/10 blur-3xl" />
|
<div className="absolute -top-10 -right-10 h-32 w-32 rounded-full bg-[#5AB4C6]/10 blur-3xl" />
|
||||||
<div className="absolute -bottom-10 -left-10 h-24 w-24 rounded-full bg-white/10 blur-3xl" />
|
<div className="absolute -bottom-10 -left-10 h-24 w-24 rounded-full bg-[#5AB4C6]/10 blur-3xl" />
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Sparkles className="mx-auto h-8 w-8 text-white/80" />
|
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-xl bg-[#5AB4C6]/10">
|
||||||
<h3 className="mt-3 text-xl font-extrabold text-white">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#5AB4C6"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||||
|
<polyline points="10 17 15 12 10 7" />
|
||||||
|
<line x1="15" x2="3" y1="12" y2="12" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-4 text-lg font-black text-white">
|
||||||
Join {hotelName} Today
|
Join {hotelName} Today
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 text-sm text-white/80">
|
<p className="mt-1 text-xs text-white/40">
|
||||||
Create your free account and start your adventure!
|
Create your free account and start your adventure!
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 inline-flex items-center gap-2 rounded-xl bg-white/20 px-6 py-2.5 text-sm font-bold text-white backdrop-blur-sm transition-all duration-200 group-hover:bg-white/30">
|
<div className="mt-5 inline-flex items-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3 px-6 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20">
|
||||||
Register Now <ArrowRight className="h-4 w-4" />
|
Register Now <ArrowRight className="h-3.5 w-3.5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
@@ -58,13 +58,13 @@ export default async function UserView({
|
|||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex w-full flex-col gap-6 lg:w-3/4">
|
<div className="flex w-full flex-col gap-6 lg:w-3/4">
|
||||||
{/* Hero/Welcome Banner */}
|
{/* Hero/Welcome Banner */}
|
||||||
<div className="animate-fade-in relative overflow-hidden rounded-2xl border border-[var(--border-subtle)] bg-gradient-to-br from-[var(--color-primary)]/10 via-[var(--color-surface)] to-[var(--color-accent)]/5 shadow-elevated">
|
<div className="relative overflow-hidden rounded-3xl border border-white/[0.06] bg-[#09090b] shadow-2xl">
|
||||||
<div className="absolute -top-10 -right-10 h-48 w-48 rounded-full opacity-[0.1] blur-[80px]" style={{ background: "var(--color-primary)" }} />
|
<div className="absolute inset-0 bg-gradient-to-br from-[#5AB4C6]/5 via-transparent to-transparent" />
|
||||||
<div className="absolute -bottom-10 -left-10 h-36 w-36 rounded-full opacity-[0.06] blur-[60px]" style={{ background: "var(--color-accent)" }} />
|
<div className="absolute -top-20 -right-20 w-64 h-64 bg-[#5AB4C6]/5 rounded-full blur-[100px]" />
|
||||||
<div className="relative flex flex-col items-center gap-6 px-6 py-8 md:flex-row md:px-8">
|
<div className="relative flex flex-col items-center gap-6 px-6 py-8 md:flex-row md:px-8">
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="rounded-2xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5 p-1 shadow-lg">
|
<div className="rounded-2xl bg-white/5 border border-white/10 p-1 shadow-lg">
|
||||||
<Image
|
<Image
|
||||||
src={avatarImageUrl(imager, look, { size: "l", direction: 2 })}
|
src={avatarImageUrl(imager, look, { size: "l", direction: 2 })}
|
||||||
alt={username}
|
alt={username}
|
||||||
@@ -75,29 +75,33 @@ export default async function UserView({
|
|||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute -bottom-2 -right-2 flex h-7 w-7 items-center justify-center rounded-full bg-green-500 shadow-lg ring-2 ring-[var(--color-surface)]">
|
<div className="absolute -bottom-2 -right-2 flex h-7 w-7 items-center justify-center rounded-full bg-green-500 shadow-lg ring-2 ring-[#09090b]">
|
||||||
<span className="h-3 w-3 rounded-full bg-green-200 animate-ping absolute" />
|
<span className="h-3 w-3 rounded-full bg-green-200 animate-ping absolute" />
|
||||||
<span className="h-3 w-3 rounded-full bg-white relative" />
|
<span className="h-3 w-3 rounded-full bg-white relative" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 text-center md:text-left">
|
<div className="flex-1 text-center md:text-left">
|
||||||
<h1 className="m-0 text-2xl font-extrabold tracking-tight">
|
<h1 className="m-0 text-2xl font-black tracking-tight text-white">
|
||||||
Welcome back, <span className="text-[var(--color-primary)]">{username}</span>
|
Welcome back,{" "}
|
||||||
|
<span className="text-[#5AB4C6]">{username}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-[var(--color-text-muted-readable)]">
|
<p className="mt-1 text-sm text-white/50">
|
||||||
{motto || "Happy to see you again!"}
|
{motto || "Happy to see you again!"}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 flex flex-wrap justify-center gap-3 md:justify-start">
|
<div className="mt-4 flex flex-wrap justify-center gap-3 md:justify-start">
|
||||||
<Link href="/client" target="_blank" rel="noopener noreferrer">
|
<Link href="/client" target="_blank" rel="noopener noreferrer">
|
||||||
<div className="inline-flex items-center gap-2 rounded-xl bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary)]/80 px-6 py-2.5 text-sm font-bold text-[var(--button-text-color-readable)] shadow-lg shadow-[var(--color-primary)]/30 transition-all duration-200 hover:scale-105 hover:shadow-glow active:scale-95">
|
<div className="inline-flex items-center gap-2 bg-[#5AB4C6] hover:bg-[#5AB4C6]/90 text-white font-black text-xs py-3 px-6 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest shadow-lg shadow-[#5AB4C6]/20">
|
||||||
<Gamepad2 size={18} />
|
<Gamepad2 size={15} />
|
||||||
{t("enterHotel", { hotelName })}
|
{t("enterHotel", { hotelName })}
|
||||||
<ArrowRight size={16} className="transition-transform group-hover:translate-x-0.5" />
|
<ArrowRight
|
||||||
|
size={14}
|
||||||
|
className="transition-transform group-hover:translate-x-0.5"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`/u/${username}`}>
|
<Link href={`/u/${username}`}>
|
||||||
<div className="inline-flex items-center gap-2 rounded-xl border-2 border-[var(--border-subtle)] bg-[var(--color-surface)] px-5 py-2.5 text-sm font-bold text-[var(--color-text-readable)] transition-all duration-200 hover:border-[var(--color-primary)]/30 hover:shadow-md">
|
<div className="inline-flex items-center gap-2 bg-white/5 hover:bg-white/10 text-white font-black text-xs py-3 px-6 rounded-lg transition-all active:scale-[0.98] uppercase tracking-widest border border-white/10">
|
||||||
View Profile
|
View Profile
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -107,18 +111,20 @@ export default async function UserView({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Online Friends Bar */}
|
{/* Online Friends Bar */}
|
||||||
<div className="animate-slide-up rounded-2xl border border-[var(--border-subtle)] bg-[var(--color-surface)] p-1">
|
<div className="rounded-2xl border border-white/[0.06] bg-white/[0.03] p-1">
|
||||||
<div className="flex flex-col gap-2 lg:flex-row lg:items-center">
|
<div className="flex flex-col gap-2 lg:flex-row lg:items-center">
|
||||||
<div className="flex shrink-0 items-center gap-2 rounded-xl bg-gradient-to-r from-[var(--color-primary)]/10 to-[var(--color-primary)]/5 px-4 py-2.5">
|
<div className="flex shrink-0 items-center gap-2 px-4 py-2.5">
|
||||||
<div className="relative flex h-2.5 w-2.5">
|
<div className="relative flex h-2 w-2">
|
||||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75" />
|
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#5AB4C6] opacity-80" />
|
||||||
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-green-500" />
|
<span className="relative inline-flex h-2 w-2 rounded-full bg-[#5AB4C6]" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-bold text-[var(--color-text-readable)]">{t("onlineFriends")}</span>
|
<span className="text-[10px] font-black uppercase tracking-widest text-white/60">
|
||||||
|
{t("onlineFriends")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2 px-3 py-1">
|
<div className="flex flex-wrap items-center gap-2 px-3 py-1">
|
||||||
{onlineFriends.length === 0 ? (
|
{onlineFriends.length === 0 ? (
|
||||||
<p className="m-0 text-xs font-medium text-[var(--color-text-muted-readable)]">
|
<p className="m-0 text-xs font-medium text-white/40">
|
||||||
{t("noFriendsOnline")}
|
{t("noFriendsOnline")}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -128,7 +134,7 @@ export default async function UserView({
|
|||||||
href={`/u/${f.username}`}
|
href={`/u/${f.username}`}
|
||||||
className="group relative transition-all duration-200 hover:scale-110"
|
className="group relative transition-all duration-200 hover:scale-110"
|
||||||
>
|
>
|
||||||
<div className="overflow-hidden rounded-xl border-2 border-transparent bg-[var(--color-background)] transition-all group-hover:border-green-500/30">
|
<div className="overflow-hidden rounded-xl border-2 border-transparent bg-white/5 transition-all group-hover:border-[#5AB4C6]/30">
|
||||||
<Image
|
<Image
|
||||||
src={avatarImageUrl(imager, f.look, { headOnly: true })}
|
src={avatarImageUrl(imager, f.look, { headOnly: true })}
|
||||||
alt={f.username}
|
alt={f.username}
|
||||||
@@ -139,7 +145,7 @@ export default async function UserView({
|
|||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-[var(--color-surface)] px-2 py-0.5 text-[0.5rem] font-bold text-[var(--color-text-muted)] opacity-0 shadow-lg transition-all group-hover:opacity-100">
|
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-[#09090b] px-2 py-0.5 text-[0.5rem] font-bold text-white/60 opacity-0 shadow-lg transition-all group-hover:opacity-100 border border-white/10">
|
||||||
{f.username}
|
{f.username}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -153,10 +159,14 @@ export default async function UserView({
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="flex w-full flex-col gap-6 lg:w-1/4">
|
<div className="flex w-full flex-col gap-6 lg:w-1/4">
|
||||||
{latestArticle ? (
|
{latestArticle ? (
|
||||||
<div className="animate-slide-up overflow-hidden rounded-2xl border border-[var(--border-subtle)] bg-[var(--color-surface)] shadow-card transition-all duration-200 hover:shadow-card-hover">
|
<div className="overflow-hidden rounded-2xl border border-white/[0.06] bg-white/[0.03] transition-all duration-200 hover:bg-white/[0.06]">
|
||||||
<div className="flex items-center gap-2 bg-gradient-to-r from-[var(--color-primary)]/10 to-[var(--color-primary)]/5 px-4 py-3 border-b border-[var(--border-subtle)]">
|
<div className="flex items-center gap-2 px-4 py-3 border-b border-white/[0.06]">
|
||||||
<Newspaper className="h-4 w-4 text-[var(--color-primary)]" />
|
<div className="flex h-6 w-6 items-center justify-center rounded-lg bg-[#5AB4C6]/10">
|
||||||
<h2 className="m-0 text-sm font-bold">{t("latestArticle")}</h2>
|
<Newspaper className="h-3 w-3 text-[#5AB4C6]" />
|
||||||
|
</div>
|
||||||
|
<h2 className="m-0 text-[11px] font-black text-white uppercase tracking-wider">
|
||||||
|
{t("latestArticle")}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<Link href={`/news/${latestArticle.slug}`} className="group block">
|
<Link href={`/news/${latestArticle.slug}`} className="group block">
|
||||||
<div className="relative aspect-[16/9] overflow-hidden">
|
<div className="relative aspect-[16/9] overflow-hidden">
|
||||||
@@ -167,16 +177,18 @@ export default async function UserView({
|
|||||||
className="object-cover transition-transform duration-500 group-hover:scale-110"
|
className="object-cover transition-transform duration-500 group-hover:scale-110"
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
<div className="absolute inset-0 bg-gradient-to-t from-[#09090b] via-black/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h3 className="text-sm font-bold leading-tight">{latestArticle.title}</h3>
|
<h3 className="text-sm font-bold text-white leading-tight">
|
||||||
|
{latestArticle.title}
|
||||||
|
</h3>
|
||||||
{latestArticle.shortStory && (
|
{latestArticle.shortStory && (
|
||||||
<p className="mt-1.5 text-xs text-[var(--color-text-muted-readable)] leading-relaxed">
|
<p className="mt-1.5 text-xs text-white/40 leading-relaxed">
|
||||||
{excerpt(latestArticle.shortStory, 100)}
|
{excerpt(latestArticle.shortStory, 100)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 flex items-center gap-1 text-xs font-bold text-[var(--color-primary)]">
|
<div className="mt-3 flex items-center gap-1 text-[10px] font-black uppercase tracking-widest text-[#5AB4C6]">
|
||||||
Read more <ArrowRight size={12} />
|
Read more <ArrowRight size={12} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,13 +197,30 @@ export default async function UserView({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Quick Stats */}
|
{/* Quick Stats */}
|
||||||
<div className="animate-slide-up rounded-2xl border border-[var(--border-subtle)] bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-accent)]/5 p-5 text-center" style={{ animationDelay: "100ms" }}>
|
<div className="rounded-2xl border border-white/[0.06] bg-white/[0.03] p-5 text-center">
|
||||||
<Sparkles className="mx-auto h-6 w-6 text-[var(--color-primary)]" />
|
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-xl bg-[#5AB4C6]/10">
|
||||||
<p className="mt-2 text-sm font-bold text-[var(--color-text-readable)]">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#5AB4C6"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||||
|
<polyline points="10 17 15 12 10 7" />
|
||||||
|
<line x1="15" x2="3" y1="12" y2="12" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-sm font-black text-white tracking-wide">
|
||||||
Explore the Hotel
|
Explore the Hotel
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs text-[var(--color-text-muted-readable)]">
|
<p className="mt-1 text-xs text-white/40">
|
||||||
Check out the latest news, visit the marketplace, or chat with friends!
|
Check out the latest news, visit the marketplace, or chat with
|
||||||
|
friends!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user