Auth: email/Google "coming soon"; mobile polish pass on the table

- AuthScreen: phone+OTP is the only active method; email & Google shown as
  disabled "coming soon" buttons (until SMTP/Gmail are configured)
- GameTable responsive: compact scoreboard, smaller seat avatars, and
  turn-indicator/timer positions tuned for small screens

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 16:11:30 +03:30
parent 0b376aec16
commit fe136f7ee4
3 changed files with 28 additions and 103 deletions
+19 -96
View File
@@ -1,22 +1,16 @@
"use client";
import { motion } from "framer-motion";
import { Mail, Phone } from "lucide-react";
import { Mail } from "lucide-react";
import { useState } from "react";
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
import { useSessionStore } from "@/lib/session-store";
import { useUIStore } from "@/lib/ui-store";
import { useI18n } from "@/lib/i18n";
import { cn } from "@/lib/cn";
type Tab = "phone" | "email";
export function AuthScreen() {
const { t } = useI18n();
const go = useUIStore((s) => s.go);
const s = useSessionStore();
const [tab, setTab] = useState<Tab>("phone");
const done = () => go("online");
return (
@@ -25,51 +19,39 @@ export function AuthScreen() {
<div className="glass rounded-3xl p-6 max-w-md mx-auto">
<p className="text-center text-cream/60 text-sm mb-5">{t("auth.subtitle")}</p>
<div className="flex gap-2 p-1 rounded-xl bg-navy-900/70 mb-5">
<TabBtn active={tab === "phone"} onClick={() => setTab("phone")} icon={<Phone className="size-4" />} label={t("auth.phone")} />
<TabBtn active={tab === "email"} onClick={() => setTab("email")} icon={<Mail className="size-4" />} label={t("auth.email")} />
</div>
{/* Active method: phone OTP */}
<PhoneForm onDone={done} />
{tab === "phone" ? <PhoneForm onDone={done} /> : <EmailForm onDone={done} />}
<div className="mt-5 pt-5 border-t border-gold-500/15">
<button
onClick={async () => {
await s.signInGoogle();
done();
}}
className="w-full rounded-xl bg-white text-slate-800 font-bold py-3 flex items-center justify-center gap-2 hover:bg-white/90 transition"
>
<GoogleIcon />
{t("auth.google")}
</button>
{/* Coming soon (until email SMTP / Google are configured) */}
<div className="mt-6 pt-5 border-t border-gold-500/15 space-y-2">
<p className="text-center text-[11px] text-cream/40 mb-1">{t("auth.otherSoon")}</p>
<SoonButton icon={<Mail className="size-4" />} label={t("auth.email")} soon={t("common.soon")} />
<SoonButton icon={<GoogleIcon />} label={t("auth.google")} soon={t("common.soon")} />
</div>
</div>
</ScreenShell>
);
}
function TabBtn({
active,
onClick,
function SoonButton({
icon,
label,
soon,
}: {
active: boolean;
onClick: () => void;
icon: React.ReactNode;
label: string;
soon: string;
}) {
return (
<button
onClick={onClick}
className={cn(
"flex-1 rounded-lg py-2 text-sm font-bold flex items-center justify-center gap-1.5 transition",
active ? "btn-gold" : "text-cream/60 hover:text-cream"
)}
disabled
className="w-full rounded-xl bg-navy-900/50 border border-gold-500/15 text-cream/40 py-3 flex items-center justify-center gap-2 cursor-not-allowed"
>
{icon}
<span className="opacity-50">{icon}</span>
{label}
<span className="text-[10px] rounded-full bg-gold-500/15 text-gold-300/80 px-2 py-0.5">
{soon}
</span>
</button>
);
}
@@ -105,6 +87,7 @@ function PhoneForm({ onDone }: { onDone: () => void }) {
<label className="block text-xs text-cream/55 mb-1.5">{t("auth.phoneLabel")}</label>
<input
dir="ltr"
inputMode="numeric"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder={t("auth.phonePlaceholder")}
@@ -125,6 +108,7 @@ function PhoneForm({ onDone }: { onDone: () => void }) {
<label className="block text-xs text-cream/55 mb-1.5">{t("auth.codeLabel")}</label>
<input
dir="ltr"
inputMode="numeric"
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder={t("auth.codePlaceholder")}
@@ -142,67 +126,6 @@ function PhoneForm({ onDone }: { onDone: () => void }) {
);
}
function EmailForm({ onDone }: { onDone: () => void }) {
const { t } = useI18n();
const signInEmail = useSessionStore((s) => s.signInEmail);
const signUpEmail = useSessionStore((s) => s.signUpEmail);
const [mode, setMode] = useState<"in" | "up">("in");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const submit = async () => {
if (!email.trim() || !password.trim()) return;
if (mode === "in") await signInEmail(email.trim(), password);
else await signUpEmail(email.trim(), password, name.trim());
onDone();
};
return (
<div className="space-y-3">
{mode === "up" && (
<div>
<label className="block text-xs text-cream/55 mb-1.5">{t("auth.nameLabel")}</label>
<input
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full rounded-xl bg-navy-900/70 gold-border px-4 py-3 text-cream outline-none focus:ring-2 focus:ring-gold-500/40"
/>
</div>
)}
<div>
<label className="block text-xs text-cream/55 mb-1.5">{t("auth.emailLabel")}</label>
<input
dir="ltr"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-xl bg-navy-900/70 gold-border px-4 py-3 text-cream outline-none focus:ring-2 focus:ring-gold-500/40"
/>
</div>
<div>
<label className="block text-xs text-cream/55 mb-1.5">{t("auth.passLabel")}</label>
<input
dir="ltr"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-xl bg-navy-900/70 gold-border px-4 py-3 text-cream outline-none focus:ring-2 focus:ring-gold-500/40"
/>
</div>
<button onClick={submit} className="btn-gold w-full rounded-xl py-3">
{mode === "in" ? t("auth.signIn") : t("auth.signUp")}
</button>
<button
onClick={() => setMode(mode === "in" ? "up" : "in")}
className="w-full text-center text-sm text-cream/55 hover:text-cream"
>
{mode === "in" ? t("auth.toggleSignup") : t("auth.toggleSignin")}
</button>
</div>
);
}
function GoogleIcon() {
return (
<svg className="size-4" viewBox="0 0 24 24">