Skip to content

Commit 90174c0

Browse files
committed
refactor(web): unify Memos instance setup into a single connection dialog
Collapse the 3-step onboarding wizard and the separate connection dialog into one dialog with a merged "Connect" action that tests the instance then saves only on success. Add token reveal/paste, a dynamic link to the instance's token settings, and an inline success state. Also correct the token-privacy copy: stats and connection-testing run client-side with the real credentials, so the token does reach the browser. Reword to "stored privately with your account and only used to reach your own instance."
1 parent 9d772b1 commit 90174c0

9 files changed

Lines changed: 271 additions & 424 deletions

src/features/dashboard/components/connect-onboarding.test.tsx

Lines changed: 0 additions & 105 deletions
This file was deleted.

src/features/dashboard/components/connect-onboarding.tsx

Lines changed: 0 additions & 155 deletions
This file was deleted.

src/features/dashboard/components/connect-prompt.test.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,27 @@ import { render, screen } from "@testing-library/react";
22
import userEvent from "@testing-library/user-event";
33
import { beforeEach, describe, expect, it, vi } from "vitest";
44

5-
vi.mock("@/shared/settings/memos-settings-client", () => ({
6-
saveMemosSettings: vi.fn(),
7-
getMemosSettings: vi.fn(),
8-
deleteMemosSettings: vi.fn(),
9-
MemosSettingsRequestError: class MemosSettingsRequestError extends Error {},
10-
}));
11-
125
import { ConnectPrompt } from "./connect-prompt";
136

14-
const notConnected = { instanceUrl: null, hasAccessToken: false };
15-
167
describe("ConnectPrompt", () => {
178
beforeEach(() => {
189
vi.clearAllMocks();
1910
});
2011

2112
it("shows the setup notice over blurred sample stats", () => {
22-
render(<ConnectPrompt settings={notConnected} onComplete={vi.fn()} />);
13+
render(<ConnectPrompt onSetUp={vi.fn()} />);
2314

2415
expect(screen.getByText("Set up your Memos instance")).toBeInTheDocument();
2516
// Sample stat tiles render behind the blur.
2617
expect(screen.getByText("Total memos")).toBeInTheDocument();
2718
});
2819

29-
it("opens the guide from the notice and returns via Maybe later", async () => {
20+
it("opens the connection dialog from the notice", async () => {
21+
const onSetUp = vi.fn();
3022
const user = userEvent.setup();
31-
render(<ConnectPrompt settings={notConnected} onComplete={vi.fn()} />);
23+
render(<ConnectPrompt onSetUp={onSetUp} />);
3224

3325
await user.click(screen.getByRole("button", { name: "Set up instance" }));
34-
expect(screen.getByText("Welcome to your dashboard")).toBeInTheDocument();
35-
36-
await user.click(screen.getByRole("button", { name: "Maybe later" }));
37-
expect(screen.getByText("Set up your Memos instance")).toBeInTheDocument();
26+
expect(onSetUp).toHaveBeenCalledTimes(1);
3827
});
3928
});

src/features/dashboard/components/connect-prompt.tsx

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,27 @@
11
"use client";
22

3-
import { useMemo, useState } from "react";
4-
import type { SafeMemosSettings } from "@/shared/settings/memos-settings";
3+
import { useMemo } from "react";
54
import { buildSampleStats } from "../lib/sample-stats";
65
import { ActivityHeatmap } from "./activity-heatmap";
7-
import { ConnectOnboarding } from "./connect-onboarding";
86
import { StatTiles } from "./stat-tiles";
97

108
const primaryButtonClassName =
119
"inline-flex h-9 items-center rounded-md bg-teal-600 px-4 text-sm font-medium text-white transition hover:bg-teal-700 dark:bg-teal-500 dark:text-stone-950 dark:hover:bg-teal-400";
1210

1311
type ConnectPromptProps = {
14-
/** Connection settings from the dashboard's useMemosConnection hook; null until loaded. */
15-
settings: SafeMemosSettings | null;
16-
/** Called when the user finishes the guide and enters the live dashboard. */
17-
onComplete: () => void;
12+
/** Opens the connection dialog (owned by the dashboard's useMemosConnection hook). */
13+
onSetUp: () => void;
1814
};
1915

2016
/**
2117
* Shown on the dashboard when no Memos instance is connected. Connecting is
2218
* recommended, not required, so the stats views render with blurred sample data
2319
* behind a setup notice rather than blocking the dashboard. The notice opens the
24-
* step-by-step guide, which can be dismissed ("Maybe later").
20+
* connection dialog, which can be dismissed without connecting.
2521
*/
26-
export function ConnectPrompt({ settings, onComplete }: ConnectPromptProps) {
27-
const [showGuide, setShowGuide] = useState(false);
22+
export function ConnectPrompt({ onSetUp }: ConnectPromptProps) {
2823
const sample = useMemo(() => buildSampleStats(), []);
2924

30-
if (showGuide) {
31-
return <ConnectOnboarding settings={settings} onComplete={onComplete} onCancel={() => setShowGuide(false)} />;
32-
}
33-
3425
return (
3526
<div className="relative">
3627
<div aria-hidden="true" className="pointer-events-none select-none space-y-6 blur-[3px] saturate-50">
@@ -46,7 +37,7 @@ export function ConnectPrompt({ settings, onComplete }: ConnectPromptProps) {
4637
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">
4738
Connect your self-hosted instance to replace this sample with your real activity heatmap and stats.
4839
</p>
49-
<button type="button" className={`${primaryButtonClassName} mt-5`} onClick={() => setShowGuide(true)}>
40+
<button type="button" className={`${primaryButtonClassName} mt-5`} onClick={onSetUp}>
5041
Set up instance
5142
</button>
5243
<p className="mt-3 text-xs text-stone-400 dark:text-stone-500">Recommended — you can keep exploring without it.</p>

src/features/dashboard/components/dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ function DashboardContent() {
151151
return (
152152
<DashboardShell>
153153
<DashboardHeader user={user ?? null} secondary="Not connected" onManageConnection={connection.open} />
154-
<ConnectPrompt settings={connection.settings} onComplete={handleSettingsChanged} />
154+
<ConnectPrompt onSetUp={connection.open} />
155155
{connection.dialog}
156156
</DashboardShell>
157157
);

src/features/memos/components/memos-connection-dialog.test.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ describe("MemosConnectionForm — token security + actions", () => {
2424
expect(screen.getByText(/A token is already saved\. Enter it again \(or a new one\) to save changes\./)).toBeInTheDocument();
2525
});
2626

27-
it("offers test, save, and disconnect actions when connected", () => {
27+
it("offers a single connect action plus disconnect when connected", () => {
2828
render(<MemosConnectionForm settings={connected} onSettingsChange={vi.fn()} />);
2929

30-
expect(screen.getByRole("button", { name: "Test connection" })).toBeInTheDocument();
31-
expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument();
30+
expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument();
3231
expect(screen.getByRole("button", { name: "Disconnect" })).toBeInTheDocument();
32+
// The separate "Test connection" / "Save" buttons are merged into Connect.
33+
expect(screen.queryByRole("button", { name: "Test connection" })).not.toBeInTheDocument();
34+
expect(screen.queryByRole("button", { name: "Save" })).not.toBeInTheDocument();
3335
});
3436

3537
it("hides disconnect until an instance is connected", () => {
@@ -39,8 +41,8 @@ describe("MemosConnectionForm — token security + actions", () => {
3941
});
4042

4143
describe("MemosConnectionDialog", () => {
42-
it("states that the token is stored server-side, not in the browser", () => {
44+
it("states that the token is stored privately with the user's account", () => {
4345
render(<MemosConnectionDialog open onOpenChange={vi.fn()} settings={notConnected} onSettingsChange={vi.fn()} />);
44-
expect(screen.getByText(/stored server-side and never sent to the browser/)).toBeInTheDocument();
46+
expect(screen.getByText(/stored privately with your account/)).toBeInTheDocument();
4547
});
4648
});

0 commit comments

Comments
 (0)