thread-master/frontend/src/pages/EasterEggsPage.tsx

149 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, type ReactNode } from 'react'
import { Badge, Button, Card, CopyableId, Notice, PageTitle } from '../components/ui'
import { RegisterForm } from '../components/RegisterForm'
import { useIslanderUnlock } from '../hooks/useIslanderUnlock'
import { writeIslanderUnlocked } from '../lib/islander/unlock'
import {
easterEggCatalog,
easterEggKindLabel,
type EasterEggEntry,
} from '../lib/easterEggCatalog'
const ADMIN_TOGGLE_IDS = new Set(['hidden-register', 'islander-guide'])
function EasterEggCard({
entry,
action,
}: {
entry: EasterEggEntry
action?: ReactNode
}) {
const kindTone =
entry.kind === 'keyboard' ? 'brand' : entry.kind === 'admin-page' ? 'sky' : 'neutral'
return (
<Card className="flex flex-col gap-6">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h2 className="text-lg font-bold text-ink">{entry.title}</h2>
<p className="mt-2 max-w-prose text-base leading-relaxed text-ink-secondary">{entry.summary}</p>
</div>
<Badge tone={kindTone}>{easterEggKindLabel(entry.kind)}</Badge>
</div>
{entry.password ? (
<CopyableId label="密碼(連續輸入)" value={entry.password} />
) : ADMIN_TOGGLE_IDS.has(entry.id) ? null : (
<div className="ac-slot px-5 py-5">
<div className="text-sm font-bold text-ink-secondary"></div>
<p className="mt-3 text-base text-muted"> </p>
</div>
)}
<div>
<h3 className="text-sm font-bold text-ink-secondary"></h3>
<ol className="mt-3 list-decimal space-y-2 pl-5 text-base leading-relaxed text-ink">
{entry.howTo.map((step) => (
<li key={step}>{step}</li>
))}
</ol>
</div>
{action ? <div className="flex flex-col gap-4">{action}</div> : null}
{entry.notes ? (
<p className="rounded-[var(--radius-md)] border border-line bg-surface-muted px-4 py-3 text-sm leading-relaxed text-ink-secondary">
<span className="font-bold text-ink"></span>
{entry.notes}
</p>
) : null}
</Card>
)
}
function renderEasterEggAction(
entry: EasterEggEntry,
state: {
registerOpen: boolean
setRegisterOpen: (open: boolean | ((value: boolean) => boolean)) => void
islanderOpen: boolean
},
) {
if (entry.id === 'hidden-register') {
return (
<>
<Button
type="button"
variant={state.registerOpen ? 'soft' : 'primary'}
className="self-start"
onClick={() => state.setRegisterOpen((open) => !open)}
>
{state.registerOpen ? '關閉註冊表單' : '開啟註冊表單'}
</Button>
{state.registerOpen ? (
<div className="rounded-[var(--radius-lg)] border border-line bg-surface-muted p-5">
<RegisterForm />
</div>
) : null}
</>
)
}
if (entry.id === 'islander-guide') {
return (
<>
<Button
type="button"
variant={state.islanderOpen ? 'soft' : 'primary'}
className="self-start"
onClick={() => writeIslanderUnlocked(!state.islanderOpen)}
>
{state.islanderOpen ? '關閉島民嚮導' : '開啟島民嚮導'}
</Button>
{state.islanderOpen ? (
<Notice
tone="success"
title="島民已開啟"
message="請看畫面右下角,會出現島民浮動按鈕(任務觀察站在左下角,島民在右側)。"
/>
) : null}
</>
)
}
return undefined
}
export function EasterEggsPage() {
const [registerOpen, setRegisterOpen] = useState(false)
const islanderOpen = useIslanderUnlock()
return (
<div className="mx-auto max-w-3xl">
<PageTitle
title="彩蛋手冊"
subtitle="管理員專用備忘 — 密碼與功能說明都記在這裡,免得下次忘記。"
/>
<Notice
tone="info"
title="僅管理員可見"
message="此頁不對一般會員開放。新增或修改彩蛋時,請同步更新 lib/easterEggCatalog.ts。"
/>
<div className="mt-6 flex flex-col gap-5">
{easterEggCatalog.map((entry) => (
<EasterEggCard
key={entry.id}
entry={entry}
action={renderEasterEggAction(entry, {
registerOpen,
setRegisterOpen,
islanderOpen,
})}
/>
))}
</div>
</div>
)
}