Add full-screen API-key gate
In user-key mode a browser with no stored Ollama key now gets a polished gate instead of the app shell: one masked input with a show/hide toggle, a single Continue button, inline validation errors, and a "?" help sheet with the steps to a free key. - Validates the draft key through /api/health (real 1-token probe) before saving; invalid-key and network failures read differently. - Key still lives only in this browser's localStorage; nothing server-side. - Gate wraps /, /create and deck study; server-key and local-daemon modes skip it; removing the key in Settings brings it back. - Removes the now-redundant dashboard/create API-key banner. - Monochrome corner glows (dark near-black / light near-white), 24px card, 48px field and button, gentle fade/slide-in, reduced-motion aware. - Help modal: centred dialog on desktop, bottom sheet on mobile, closes via X, Escape or backdrop. - README and browser suites updated (34 gate checks, 15 key-flow checks, 12 shipped-default checks; ui/folders/docs/pwa/security/contrast green).
This commit is contained in:
parent
532b5a18fe
commit
dfa7b7502a
13 changed files with 505 additions and 94 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import { ApiKeyGate } from "@/components/ApiKeyGate";
|
||||
import { AppHeader } from "@/components/AppHeader";
|
||||
import { MobileSidebar, Sidebar, SidebarOffset } from "@/components/Sidebar";
|
||||
import { TabBar } from "@/components/TabBar";
|
||||
|
|
@ -9,17 +10,19 @@ export const metadata = {
|
|||
|
||||
export default function CreatePage() {
|
||||
return (
|
||||
<div className="flex min-h-dvh bg-[var(--page)]">
|
||||
<Sidebar />
|
||||
<SidebarOffset />
|
||||
<MobileSidebar />
|
||||
<main className="min-w-0 flex-1">
|
||||
<AppHeader />
|
||||
<div className="mx-auto w-full max-w-3xl px-4 pb-cta lg:px-10 lg:pb-20">
|
||||
<UploadScreen />
|
||||
</div>
|
||||
<TabBar />
|
||||
</main>
|
||||
</div>
|
||||
<ApiKeyGate>
|
||||
<div className="flex min-h-dvh bg-[var(--page)]">
|
||||
<Sidebar />
|
||||
<SidebarOffset />
|
||||
<MobileSidebar />
|
||||
<main className="min-w-0 flex-1">
|
||||
<AppHeader />
|
||||
<div className="mx-auto w-full max-w-3xl px-4 pb-cta lg:px-10 lg:pb-20">
|
||||
<UploadScreen />
|
||||
</div>
|
||||
<TabBar />
|
||||
</main>
|
||||
</div>
|
||||
</ApiKeyGate>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Link from "next/link";
|
|||
import { useParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { ApiKeyGate } from "@/components/ApiKeyGate";
|
||||
import { StudyFeed } from "@/components/StudyFeed";
|
||||
import { Button, Spinner } from "@/components/ui";
|
||||
import type { Deck } from "@/lib/schemas";
|
||||
|
|
@ -62,5 +63,9 @@ export default function DeckPage() {
|
|||
);
|
||||
}
|
||||
|
||||
return <StudyFeed key={deck.id} deck={deck} />;
|
||||
return (
|
||||
<ApiKeyGate>
|
||||
<StudyFeed key={deck.id} deck={deck} />
|
||||
</ApiKeyGate>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,13 @@
|
|||
--sidebar-top: #fbfbfc;
|
||||
--sidebar-bottom: #f6f6f7;
|
||||
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(13 13 13 / 0.28);
|
||||
/* API-key gate: near-white stage with soft blank glows, tinted input. */
|
||||
--gate-base: #f7f7f8;
|
||||
--gate-glow-a: rgb(64 74 90 / 0.1);
|
||||
--gate-glow-b: rgb(96 108 124 / 0.07);
|
||||
--field: #f3f3f4;
|
||||
--field-border: #e2e2e4;
|
||||
--shadow-gate: 0 28px 70px -36px rgb(13 13 13 / 0.35), 0 1px 2px rgb(13 13 13 / 0.05);
|
||||
--shadow-card: 0 1px 2px rgb(13 13 13 / 0.06), 0 1px 3px rgb(13 13 13 / 0.04);
|
||||
--shadow-card-hover: 0 10px 28px -14px rgb(13 13 13 / 0.22), 0 1px 2px rgb(13 13 13 / 0.06);
|
||||
}
|
||||
|
|
@ -123,6 +130,13 @@
|
|||
--sidebar-top: #1a1a1a;
|
||||
--sidebar-bottom: #151515;
|
||||
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75);
|
||||
/* API-key gate: near-black stage, monochrome glows, raised input. */
|
||||
--gate-base: #0d0d0e;
|
||||
--gate-glow-a: rgb(255 255 255 / 0.075);
|
||||
--gate-glow-b: rgb(255 255 255 / 0.05);
|
||||
--field: #383838;
|
||||
--field-border: rgb(255 255 255 / 0.12);
|
||||
--shadow-gate: 0 30px 80px -40px rgb(0 0 0 / 0.9), 0 1px 2px rgb(0 0 0 / 0.4);
|
||||
--shadow-card: 0 1px 2px rgb(0 0 0 / 0.25);
|
||||
--shadow-card-hover: 0 10px 28px -16px rgb(0 0 0 / 0.55), 0 1px 2px rgb(0 0 0 / 0.3);
|
||||
}
|
||||
|
|
@ -172,6 +186,16 @@ body {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
/* API-key gate stage: monochrome radial glows in opposite corners over a
|
||||
near-black (dark) or near-white (light) base. Radial gradients are the
|
||||
softest "blurred glow" available without extra layers. */
|
||||
.gate-bg {
|
||||
background:
|
||||
radial-gradient(64rem 46rem at 112% -12%, var(--gate-glow-a), transparent 68%),
|
||||
radial-gradient(58rem 42rem at -12% 112%, var(--gate-glow-b), transparent 70%),
|
||||
var(--gate-base);
|
||||
}
|
||||
|
||||
.app-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
|
|
|||
31
app/page.tsx
31
app/page.tsx
|
|
@ -1,3 +1,4 @@
|
|||
import { ApiKeyGate } from "@/components/ApiKeyGate";
|
||||
import { AppHeader } from "@/components/AppHeader";
|
||||
import { InstallHint } from "@/components/InstallHint";
|
||||
import { LibraryScreen } from "@/components/LibraryScreen";
|
||||
|
|
@ -6,20 +7,22 @@ import { TabBar } from "@/components/TabBar";
|
|||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="flex min-h-dvh bg-[var(--page)]">
|
||||
<Sidebar />
|
||||
<SidebarOffset />
|
||||
<MobileSidebar />
|
||||
<main className="min-w-0 flex-1">
|
||||
<AppHeader />
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-tabbar lg:px-10 lg:pb-16">
|
||||
<div className="mb-6">
|
||||
<InstallHint />
|
||||
<ApiKeyGate>
|
||||
<div className="flex min-h-dvh bg-[var(--page)]">
|
||||
<Sidebar />
|
||||
<SidebarOffset />
|
||||
<MobileSidebar />
|
||||
<main className="min-w-0 flex-1">
|
||||
<AppHeader />
|
||||
<div className="mx-auto w-full max-w-6xl px-4 pb-tabbar lg:px-10 lg:pb-16">
|
||||
<div className="mb-6">
|
||||
<InstallHint />
|
||||
</div>
|
||||
<LibraryScreen />
|
||||
</div>
|
||||
<LibraryScreen />
|
||||
</div>
|
||||
<TabBar />
|
||||
</main>
|
||||
</div>
|
||||
<TabBar />
|
||||
</main>
|
||||
</div>
|
||||
</ApiKeyGate>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue