Improve authentication recovery and mobile gameplay
This commit is contained in:
@@ -284,3 +284,10 @@
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
.legacy-password-dialog { width: min(540px, calc(100vw - 24px)); }
|
||||
.legacy-password-dialog > svg { color: var(--green); }
|
||||
.legacy-password-dialog h2 { margin-top: 16px; }
|
||||
.legacy-password-dialog .field input { width: 100%; font: 14px monospace; padding: 12px; background: var(--bg); color: var(--text); border: 1px solid var(--line); }
|
||||
.legacy-password-dialog > .text-link { margin-top: 18px; }
|
||||
.password-saved { display: flex; gap: 10px; align-items: flex-start; margin: 20px 0; }
|
||||
.password-saved input { flex-shrink: 0; margin-top: 5px; accent-color: var(--green); }
|
||||
|
||||
@@ -11,11 +11,13 @@ export async function GET(request: NextRequest) {
|
||||
const legacyToken = request.nextUrl.searchParams.get("legacy_recovery_token");
|
||||
const type = request.nextUrl.searchParams.get("type");
|
||||
const next = authDestination(request.nextUrl.searchParams.get("next"));
|
||||
if (type === "magiclink")
|
||||
return NextResponse.redirect(new URL("/log-in?auth=failed", origin));
|
||||
if (
|
||||
code ||
|
||||
legacyToken ||
|
||||
(tokenHash &&
|
||||
(type === "email" || type === "recovery" || type === "magiclink"))
|
||||
(type === "email" || type === "signup" || type === "recovery"))
|
||||
) {
|
||||
const client = await authClient();
|
||||
let { data, error } = code
|
||||
@@ -24,7 +26,7 @@ export async function GET(request: NextRequest) {
|
||||
? await client.auth.exchangeCodeForSession(legacyToken)
|
||||
: await client.auth.verifyOtp({
|
||||
token_hash: tokenHash!,
|
||||
type: type as "email" | "recovery" | "magiclink",
|
||||
type: type as "email" | "signup" | "recovery",
|
||||
});
|
||||
// Older email templates could place a recovery token hash in /code=...
|
||||
// instead of a PKCE code. Only Supabase can decide whether it is valid.
|
||||
|
||||
@@ -12,8 +12,11 @@ import { SiteCursor } from "@/components/SiteCursor";
|
||||
import { I18nProvider } from "@/components/I18nProvider";
|
||||
import { getI18n } from "@/lib/i18n";
|
||||
import "./polish.css";
|
||||
import "./mobile-game.css";
|
||||
import { SiteSettingsProvider } from "@/components/SiteSettingsProvider";
|
||||
import { initialSiteSettings } from "@/lib/site-settings";
|
||||
import { user } from "@/lib/supabase";
|
||||
import { LegacyPasswordNotice } from "@/components/LegacyPasswordNotice";
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const { t } = await getI18n();
|
||||
return {
|
||||
@@ -31,10 +34,8 @@ export default async function RootLayout({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [{ locale, preference, detectedLocale }, settings] = await Promise.all([
|
||||
getI18n(),
|
||||
initialSiteSettings(),
|
||||
]);
|
||||
const [{ locale, preference, detectedLocale }, settings, account] =
|
||||
await Promise.all([getI18n(), initialSiteSettings(), user()]);
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
@@ -46,6 +47,7 @@ export default async function RootLayout({
|
||||
<SiteSettingsProvider initialSettings={settings}>
|
||||
{children}
|
||||
<SiteCursor />
|
||||
<LegacyPasswordNotice enabled={!!account?.email} />
|
||||
</SiteSettingsProvider>
|
||||
</I18nProvider>
|
||||
</body>
|
||||
|
||||
192
app/mobile-game.css
Normal file
192
app/mobile-game.css
Normal file
@@ -0,0 +1,192 @@
|
||||
.mobile-map-toggle,
|
||||
.report-icon {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.game-page {
|
||||
min-height: 0;
|
||||
}
|
||||
.game-header {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 44px;
|
||||
gap: 8px;
|
||||
padding: max(10px, env(safe-area-inset-top))
|
||||
max(10px, env(safe-area-inset-right)) 12px
|
||||
max(10px, env(safe-area-inset-left));
|
||||
}
|
||||
.game-header .brand {
|
||||
font-size: 17px;
|
||||
white-space: nowrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.game-header .brand-cube {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.game-header .report-trigger {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
}
|
||||
.game-header .report-trigger > span {
|
||||
display: none;
|
||||
}
|
||||
.game-header .report-icon {
|
||||
display: block;
|
||||
}
|
||||
.game-info {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.game-info > div {
|
||||
min-width: 0;
|
||||
}
|
||||
.game-info small {
|
||||
white-space: nowrap;
|
||||
font-size: 8px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.game-info strong {
|
||||
white-space: nowrap;
|
||||
font-size: 17px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.panorama-controls {
|
||||
top: calc(max(10px, env(safe-area-inset-top)) + 112px);
|
||||
left: max(10px, env(safe-area-inset-left));
|
||||
gap: 6px;
|
||||
}
|
||||
.panorama-controls button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
padding: 0;
|
||||
}
|
||||
.game-page .game-map,
|
||||
.game-page .game-map.expanded {
|
||||
right: max(8px, env(safe-area-inset-right));
|
||||
left: max(8px, env(safe-area-inset-left));
|
||||
bottom: max(10px, env(safe-area-inset-bottom));
|
||||
width: auto;
|
||||
max-width: none;
|
||||
box-shadow: 0 8px 24px #0006;
|
||||
}
|
||||
.game-map:not(.expanded) .game-map-content {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.mobile-map-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
color: #fff1d0;
|
||||
background: #241e18;
|
||||
font: 14px var(--font-pixel, monospace);
|
||||
}
|
||||
.mobile-map-toggle small {
|
||||
font: 11px monospace;
|
||||
}
|
||||
.game-map.expanded .mobile-map-toggle {
|
||||
display: none;
|
||||
}
|
||||
.game-map .map-topline {
|
||||
min-height: 44px;
|
||||
padding: 0 8px 0 12px;
|
||||
}
|
||||
.game-map .map-topline button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.game-page .game-map .map-surface {
|
||||
height: min(46dvh, 360px);
|
||||
border-width: 12px;
|
||||
border-image-width: 12px;
|
||||
}
|
||||
.game-map .map-actions {
|
||||
padding: 8px 10px 10px;
|
||||
}
|
||||
.game-map .map-actions .button {
|
||||
min-height: 48px;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.game-map .map-actions small {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.game-map .leaflet-bar a {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
.look-help {
|
||||
display: none;
|
||||
}
|
||||
.round-result {
|
||||
max-width: calc(100% - 24px);
|
||||
max-height: calc(100dvh - 140px);
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
html[lang] .round-result h2 {
|
||||
font-size: 21px;
|
||||
}
|
||||
.round-result .score {
|
||||
font-size: 42px;
|
||||
}
|
||||
}
|
||||
@media (max-height: 500px) and (max-width: 950px) {
|
||||
.game-page {
|
||||
min-height: 0;
|
||||
}
|
||||
.game-header {
|
||||
display: flex;
|
||||
padding: 8px 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
.game-header .brand {
|
||||
font-size: 16px;
|
||||
}
|
||||
.game-header .report-trigger {
|
||||
order: 3;
|
||||
}
|
||||
.game-info {
|
||||
width: auto;
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
margin-left: auto;
|
||||
gap: 16px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.panorama-controls {
|
||||
top: 72px;
|
||||
left: 12px;
|
||||
}
|
||||
.game-page .game-map,
|
||||
.game-page .game-map.expanded {
|
||||
left: auto;
|
||||
width: min(440px, calc(100vw - 80px));
|
||||
right: 8px;
|
||||
bottom: max(8px, env(safe-area-inset-bottom));
|
||||
}
|
||||
.game-page .game-map .map-surface {
|
||||
height: max(100px, calc(100dvh - 190px));
|
||||
}
|
||||
.round-result {
|
||||
max-height: calc(100dvh - 80px);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user