plotlink-ows 0.1.15 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -93
- package/app/db.ts +1 -1
- package/app/lib/paths.ts +0 -2
- package/app/lib/publish.ts +257 -44
- package/app/prisma/schema.prisma +0 -36
- package/app/routes/dashboard.ts +105 -57
- package/app/routes/publish.ts +107 -25
- package/app/routes/settings.ts +194 -0
- package/app/routes/stories.ts +223 -0
- package/app/routes/terminal.ts +258 -0
- package/app/routes/wallet.ts +40 -10
- package/app/server.ts +35 -81
- package/app/web/App.tsx +4 -6
- package/app/web/components/Dashboard.tsx +98 -79
- package/app/web/components/Layout.tsx +70 -103
- package/app/web/components/PreviewPanel.tsx +388 -0
- package/app/web/components/Settings.tsx +210 -67
- package/app/web/components/StoriesPage.tsx +270 -0
- package/app/web/components/StoryBrowser.tsx +161 -0
- package/app/web/components/TerminalPanel.tsx +428 -0
- package/app/web/components/WalletCard.tsx +14 -8
- package/app/web/dist/assets/index-BuOxhUWG.css +32 -0
- package/app/web/dist/assets/index-De8CpT47.js +129 -0
- package/app/web/dist/index.html +3 -3
- package/app/web/dist/plotlink-logo.svg +5 -0
- package/app/web/public/plotlink-logo.svg +5 -0
- package/app/web/styles.css +18 -0
- package/bin/plotlink-ows.js +18 -62
- package/package.json +15 -6
- package/scripts/fix-index-status.ts +93 -0
- package/app/lib/llm-client.ts +0 -265
- package/app/lib/writer-prompt.ts +0 -44
- package/app/routes/chat.ts +0 -135
- package/app/routes/config.ts +0 -210
- package/app/routes/oauth.ts +0 -150
- package/app/web/components/Chat.tsx +0 -272
- package/app/web/components/LLMSetup.tsx +0 -291
- package/app/web/components/Publish.tsx +0 -245
- package/app/web/dist/assets/index-C9kXlYO_.css +0 -2
- package/app/web/dist/assets/index-CJiiaLHs.js +0 -9
|
@@ -9,17 +9,27 @@ interface WalletInfo {
|
|
|
9
9
|
usdcBalance: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
interface
|
|
12
|
+
interface StoryFile {
|
|
13
|
+
file: string;
|
|
14
|
+
status: string;
|
|
15
|
+
txHash?: string | null;
|
|
16
|
+
gasCostEth?: string | null;
|
|
17
|
+
publishedAt?: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface StoryGroup {
|
|
13
21
|
id: string;
|
|
14
22
|
title: string;
|
|
15
23
|
genre: string | null;
|
|
16
|
-
|
|
17
|
-
txHash?: string | null;
|
|
24
|
+
storyName: string;
|
|
18
25
|
storylineId?: number | null;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
plotCount: number;
|
|
27
|
+
publishedFiles: number;
|
|
28
|
+
hasNotIndexed: boolean;
|
|
29
|
+
totalGasCostEth?: string | null;
|
|
30
|
+
totalGasCostUsd?: string | null;
|
|
31
|
+
latestPublishedAt?: string | null;
|
|
32
|
+
files: StoryFile[];
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
interface DashboardData {
|
|
@@ -28,18 +38,16 @@ interface DashboardData {
|
|
|
28
38
|
royalties: { earned: string; claimed: string; unclaimed: string; token: string };
|
|
29
39
|
pnl: { totalCostsEth: string; totalCostsUsd: string; totalRoyaltiesPlot: string; totalRoyaltiesUsd: string; netPnlUsd: string; plotUsdPrice: string };
|
|
30
40
|
stories: {
|
|
31
|
-
published:
|
|
32
|
-
drafts: Story[];
|
|
41
|
+
published: StoryGroup[];
|
|
33
42
|
totalPublished: number;
|
|
34
|
-
|
|
43
|
+
totalStories: number;
|
|
44
|
+
totalFiles: number;
|
|
45
|
+
pendingFiles: number;
|
|
35
46
|
};
|
|
36
|
-
sessions: { total: number; totalMessages: number };
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
export function Dashboard({ token }: { token: string }) {
|
|
40
50
|
const [data, setData] = useState<DashboardData | null>(null);
|
|
41
|
-
const [deleting, setDeleting] = useState<string | null>(null);
|
|
42
|
-
|
|
43
51
|
const authFetch = (url: string, opts?: RequestInit) =>
|
|
44
52
|
fetch(url, { ...opts, headers: { ...opts?.headers, Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
|
|
45
53
|
|
|
@@ -51,15 +59,13 @@ export function Dashboard({ token }: { token: string }) {
|
|
|
51
59
|
|
|
52
60
|
useEffect(() => { loadDashboard(); }, []);
|
|
53
61
|
|
|
54
|
-
const handleDelete = async (id: string) => {
|
|
55
|
-
setDeleting(id);
|
|
56
|
-
await authFetch(`${API_BASE}/api/dashboard/drafts/${id}`, { method: "DELETE" });
|
|
57
|
-
loadDashboard();
|
|
58
|
-
setDeleting(null);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
62
|
const truncate = (addr: string) => `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
|
62
|
-
const formatDate = (d: string
|
|
63
|
+
const formatDate = (d: string | undefined | null) => {
|
|
64
|
+
if (!d) return "Unknown date";
|
|
65
|
+
const date = new Date(d);
|
|
66
|
+
if (isNaN(date.getTime())) return "Unknown date";
|
|
67
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
68
|
+
};
|
|
63
69
|
|
|
64
70
|
if (!data) {
|
|
65
71
|
return (
|
|
@@ -80,16 +86,16 @@ export function Dashboard({ token }: { token: string }) {
|
|
|
80
86
|
<div className="text-muted text-[10px] uppercase tracking-wider">published</div>
|
|
81
87
|
</div>
|
|
82
88
|
<div className="border-border rounded border p-3 text-center">
|
|
83
|
-
<div className="text-foreground text-lg font-bold">{data.stories.
|
|
84
|
-
<div className="text-muted text-[10px] uppercase tracking-wider">
|
|
89
|
+
<div className="text-foreground text-lg font-bold">{data.stories.pendingFiles}</div>
|
|
90
|
+
<div className="text-muted text-[10px] uppercase tracking-wider">pending</div>
|
|
85
91
|
</div>
|
|
86
92
|
<div className="border-border rounded border p-3 text-center">
|
|
87
|
-
<div className="text-foreground text-lg font-bold">{data.
|
|
88
|
-
<div className="text-muted text-[10px] uppercase tracking-wider">
|
|
93
|
+
<div className="text-foreground text-lg font-bold">{data.stories.totalStories}</div>
|
|
94
|
+
<div className="text-muted text-[10px] uppercase tracking-wider">stories</div>
|
|
89
95
|
</div>
|
|
90
96
|
<div className="border-border rounded border p-3 text-center">
|
|
91
|
-
<div className="text-foreground text-lg font-bold">{data.
|
|
92
|
-
<div className="text-muted text-[10px] uppercase tracking-wider">
|
|
97
|
+
<div className="text-foreground text-lg font-bold">{data.stories.totalFiles}</div>
|
|
98
|
+
<div className="text-muted text-[10px] uppercase tracking-wider">files</div>
|
|
93
99
|
</div>
|
|
94
100
|
</div>
|
|
95
101
|
|
|
@@ -149,38 +155,74 @@ export function Dashboard({ token }: { token: string }) {
|
|
|
149
155
|
{data.stories.published.length === 0 ? (
|
|
150
156
|
<p className="text-muted text-xs">no published stories yet</p>
|
|
151
157
|
) : (
|
|
152
|
-
<div className="space-y-
|
|
158
|
+
<div className="space-y-3">
|
|
153
159
|
{data.stories.published.map((story) => (
|
|
154
|
-
<div key={story.id} className="bg-surface rounded p-
|
|
155
|
-
<div className="flex items-
|
|
160
|
+
<div key={story.id} className="bg-surface rounded border border-border p-4">
|
|
161
|
+
<div className="flex items-start justify-between">
|
|
156
162
|
<div>
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
{story.genre && (
|
|
164
|
+
<span className="bg-accent/10 text-accent rounded px-2 py-0.5 text-[10px] font-medium">{story.genre}</span>
|
|
165
|
+
)}
|
|
166
|
+
<h4 className="text-foreground mt-1 text-sm font-serif font-medium">{story.title}</h4>
|
|
167
|
+
<p className="text-muted mt-0.5 text-[10px] font-mono">{story.storyName}</p>
|
|
159
168
|
</div>
|
|
160
169
|
<div className="flex items-center gap-2">
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<a
|
|
164
|
-
href={`https://plotlink.xyz/story/${story.storylineId}`}
|
|
165
|
-
target="_blank"
|
|
166
|
-
rel="noopener noreferrer"
|
|
167
|
-
className="text-accent text-[10px] underline"
|
|
168
|
-
>
|
|
169
|
-
view
|
|
170
|
-
</a>
|
|
171
|
-
) : (
|
|
172
|
-
<a href="https://plotlink.xyz" target="_blank" rel="noopener noreferrer" className="text-accent text-[10px] underline">plotlink.xyz</a>
|
|
170
|
+
{story.hasNotIndexed && (
|
|
171
|
+
<span className="rounded border border-amber-600/30 px-1.5 py-0.5 text-[9px] text-amber-700">not indexed</span>
|
|
173
172
|
)}
|
|
173
|
+
<span className="rounded border border-green-700/30 px-1.5 py-0.5 text-[9px] text-green-700">
|
|
174
|
+
{story.publishedFiles} published
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="mt-2 grid grid-cols-3 gap-2 text-center">
|
|
179
|
+
<div className="rounded bg-background p-1.5">
|
|
180
|
+
<div className="text-foreground text-sm font-medium">{story.plotCount}</div>
|
|
181
|
+
<div className="text-muted text-[9px]">Plots</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div className="rounded bg-background p-1.5">
|
|
184
|
+
<div className="text-foreground text-sm font-medium font-mono">
|
|
185
|
+
{story.storylineId ? `#${story.storylineId}` : "—"}
|
|
186
|
+
</div>
|
|
187
|
+
<div className="text-muted text-[9px]">Storyline</div>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="rounded bg-background p-1.5">
|
|
190
|
+
<div className="text-foreground text-sm font-medium">
|
|
191
|
+
{story.totalGasCostEth ?? "—"}
|
|
192
|
+
</div>
|
|
193
|
+
<div className="text-muted text-[9px]">Gas (ETH)</div>
|
|
174
194
|
</div>
|
|
175
195
|
</div>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{story.
|
|
179
|
-
<
|
|
180
|
-
|
|
196
|
+
{/* Individual files */}
|
|
197
|
+
<div className="mt-2 space-y-1">
|
|
198
|
+
{story.files.map((f) => (
|
|
199
|
+
<div key={f.file} className="flex items-center justify-between text-[10px]">
|
|
200
|
+
<div className="flex items-center gap-1.5">
|
|
201
|
+
<span className={f.status === "published-not-indexed" ? "text-amber-700" : "text-green-700"}>
|
|
202
|
+
{f.status === "published-not-indexed" ? "\u26A0" : "\u2713"}
|
|
203
|
+
</span>
|
|
204
|
+
<span className="text-muted font-mono">{f.file}</span>
|
|
205
|
+
</div>
|
|
206
|
+
{f.txHash && (
|
|
207
|
+
<a href={`https://basescan.org/tx/${f.txHash}`} target="_blank" rel="noopener noreferrer" className="text-muted hover:text-accent font-mono">
|
|
208
|
+
tx:{f.txHash.slice(0, 8)}...
|
|
209
|
+
</a>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
<div className="mt-2 flex items-center justify-between text-[10px]">
|
|
215
|
+
<span className="text-muted">{formatDate(story.latestPublishedAt)}</span>
|
|
216
|
+
{story.storylineId && (
|
|
217
|
+
<a
|
|
218
|
+
href={`https://plotlink.xyz/story/${story.storylineId}`}
|
|
219
|
+
target="_blank"
|
|
220
|
+
rel="noopener noreferrer"
|
|
221
|
+
className="text-accent underline"
|
|
222
|
+
>
|
|
223
|
+
View on PlotLink
|
|
181
224
|
</a>
|
|
182
225
|
)}
|
|
183
|
-
{story.gasCostEth && <span className="text-muted">{story.gasCostEth} ETH{story.gasCostUsd ? ` (~$${story.gasCostUsd})` : ""}</span>}
|
|
184
226
|
</div>
|
|
185
227
|
</div>
|
|
186
228
|
))}
|
|
@@ -188,35 +230,12 @@ export function Dashboard({ token }: { token: string }) {
|
|
|
188
230
|
)}
|
|
189
231
|
</div>
|
|
190
232
|
|
|
191
|
-
{/*
|
|
192
|
-
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
<div className="space-y-2">
|
|
198
|
-
{data.stories.drafts.map((draft) => (
|
|
199
|
-
<div key={draft.id} className="bg-surface flex items-center justify-between rounded p-3">
|
|
200
|
-
<div>
|
|
201
|
-
<span className="text-foreground text-sm font-medium">{draft.title}</span>
|
|
202
|
-
{draft.genre && <span className="text-accent ml-2 text-[10px]">{draft.genre}</span>}
|
|
203
|
-
<div className="text-muted text-[10px]">{formatDate(draft.createdAt)}</div>
|
|
204
|
-
</div>
|
|
205
|
-
<div className="flex items-center gap-2">
|
|
206
|
-
<span className="border-border rounded border px-1.5 py-0.5 text-[9px] text-muted">{draft.status}</span>
|
|
207
|
-
<button
|
|
208
|
-
onClick={() => handleDelete(draft.id)}
|
|
209
|
-
disabled={deleting === draft.id}
|
|
210
|
-
className="text-muted hover:text-error text-[10px] transition-colors"
|
|
211
|
-
>
|
|
212
|
-
{deleting === draft.id ? "..." : "delete"}
|
|
213
|
-
</button>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
))}
|
|
217
|
-
</div>
|
|
218
|
-
)}
|
|
219
|
-
</div>
|
|
233
|
+
{/* Pending files info */}
|
|
234
|
+
{data.stories.pendingFiles > 0 && (
|
|
235
|
+
<div className="border-border rounded border p-4">
|
|
236
|
+
<p className="text-muted text-xs">{data.stories.pendingFiles} file(s) pending publish — go to Stories to publish them.</p>
|
|
237
|
+
</div>
|
|
238
|
+
)}
|
|
220
239
|
</div>
|
|
221
240
|
);
|
|
222
241
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { LLMSetup } from "./LLMSetup";
|
|
3
|
-
import { WalletCard } from "./WalletCard";
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
4
2
|
import { Settings } from "./Settings";
|
|
5
|
-
import { Chat } from "./Chat";
|
|
6
|
-
import { Publish } from "./Publish";
|
|
7
3
|
import { Dashboard } from "./Dashboard";
|
|
4
|
+
import { StoriesPage } from "./StoriesPage";
|
|
5
|
+
import { WalletCard } from "./WalletCard";
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type Page = "home" | "chat" | "publish" | "dashboard" | "llm-setup" | "wallet-setup" | "settings";
|
|
7
|
+
type Page = "home" | "stories" | "dashboard" | "wallet-setup" | "settings";
|
|
12
8
|
|
|
13
9
|
function WalletSetupPage({ token, onComplete }: { token: string; onComplete: () => void }) {
|
|
14
10
|
const [creating, setCreating] = useState(false);
|
|
@@ -19,7 +15,7 @@ function WalletSetupPage({ token, onComplete }: { token: string; onComplete: ()
|
|
|
19
15
|
setCreating(true);
|
|
20
16
|
setError(null);
|
|
21
17
|
try {
|
|
22
|
-
const res = await fetch(
|
|
18
|
+
const res = await fetch("/api/wallet/create", {
|
|
23
19
|
method: "POST",
|
|
24
20
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
25
21
|
});
|
|
@@ -32,12 +28,13 @@ function WalletSetupPage({ token, onComplete }: { token: string; onComplete: ()
|
|
|
32
28
|
setCreating(false);
|
|
33
29
|
};
|
|
34
30
|
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
32
|
useEffect(() => { createWallet(); }, []);
|
|
36
33
|
|
|
37
34
|
return (
|
|
38
35
|
<div className="mx-auto max-w-sm p-6 text-center">
|
|
39
36
|
<h2 className="text-accent mb-1 text-lg font-bold">Wallet Setup</h2>
|
|
40
|
-
<p className="text-muted mb-6 text-xs">creating your OWS wallet for
|
|
37
|
+
<p className="text-muted mb-6 text-xs">creating your OWS wallet for on-chain publishing</p>
|
|
41
38
|
|
|
42
39
|
{creating && <p className="text-accent text-sm">creating wallet...</p>}
|
|
43
40
|
|
|
@@ -71,64 +68,58 @@ function WalletSetupPage({ token, onComplete }: { token: string; onComplete: ()
|
|
|
71
68
|
|
|
72
69
|
export function Layout({ token, onLogout }: { token: string; onLogout: () => void }) {
|
|
73
70
|
const [page, setPage] = useState<Page>("home");
|
|
74
|
-
const [
|
|
71
|
+
const [storyCount, setStoryCount] = useState(0);
|
|
72
|
+
|
|
73
|
+
const authFetch = useCallback(async (url: string, opts?: RequestInit) => {
|
|
74
|
+
return fetch(url, {
|
|
75
|
+
...opts,
|
|
76
|
+
headers: {
|
|
77
|
+
...(opts?.headers || {}),
|
|
78
|
+
Authorization: `Bearer ${token}`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}, [token]);
|
|
75
82
|
|
|
76
83
|
useEffect(() => {
|
|
77
84
|
async function checkSetup() {
|
|
78
85
|
try {
|
|
79
|
-
// Check LLM config
|
|
80
|
-
const llmRes = await fetch(`${API_BASE}/api/config/llm`, {
|
|
81
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
82
|
-
});
|
|
83
|
-
const llmData = await llmRes.json();
|
|
84
|
-
const hasLlm = llmData.configured?.length > 0;
|
|
85
|
-
setLlmConfigured(hasLlm);
|
|
86
|
-
|
|
87
|
-
if (!hasLlm) {
|
|
88
|
-
setPage("llm-setup");
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
86
|
// Check wallet existence
|
|
93
|
-
const walletRes = await
|
|
94
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
95
|
-
});
|
|
87
|
+
const walletRes = await authFetch("/api/wallet");
|
|
96
88
|
const walletData = await walletRes.json();
|
|
97
89
|
if (!walletData.exists) {
|
|
98
90
|
setPage("wallet-setup");
|
|
99
91
|
return;
|
|
100
92
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
|
|
94
|
+
// Load story count
|
|
95
|
+
const storiesRes = await authFetch("/api/stories");
|
|
96
|
+
if (storiesRes.ok) {
|
|
97
|
+
const data = await storiesRes.json();
|
|
98
|
+
setStoryCount(data.stories.filter((s: { name: string }) => s.name !== "_example").length);
|
|
99
|
+
}
|
|
100
|
+
} catch { /* ignore */ }
|
|
104
101
|
}
|
|
105
102
|
checkSetup();
|
|
106
|
-
|
|
103
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
}, [token]);
|
|
107
105
|
|
|
108
106
|
return (
|
|
109
107
|
<div className="flex h-screen flex-col">
|
|
110
108
|
{/* Header */}
|
|
111
|
-
<header className="border-border flex items-center justify-between border-b px-4
|
|
109
|
+
<header className="border-border flex h-14 items-center justify-between border-b px-4 flex-shrink-0">
|
|
112
110
|
<div className="flex items-center gap-3">
|
|
113
111
|
<button onClick={() => { if (page !== "wallet-setup") setPage("home"); }} className="flex items-center gap-2 hover:opacity-80">
|
|
114
|
-
<img src="/plotlink-logo.svg" alt="PlotLink" className="h-5 w-5" />
|
|
115
112
|
<span className="text-accent text-sm font-bold tracking-tight">PlotLink OWS</span>
|
|
116
113
|
</button>
|
|
117
|
-
<span className="text-muted text-[10px] uppercase tracking-wider">
|
|
114
|
+
<span className="text-muted text-[10px] uppercase tracking-wider">writer</span>
|
|
118
115
|
</div>
|
|
119
116
|
{page !== "wallet-setup" && (
|
|
120
117
|
<nav className="flex items-center gap-4">
|
|
121
118
|
<button
|
|
122
|
-
onClick={() => setPage("
|
|
123
|
-
className={`text-xs transition-colors ${page === "
|
|
124
|
-
>
|
|
125
|
-
write
|
|
126
|
-
</button>
|
|
127
|
-
<button
|
|
128
|
-
onClick={() => setPage("publish")}
|
|
129
|
-
className={`text-xs transition-colors ${page === "publish" ? "text-accent" : "text-muted hover:text-foreground"}`}
|
|
119
|
+
onClick={() => setPage("stories")}
|
|
120
|
+
className={`text-xs transition-colors ${page === "stories" ? "text-accent" : "text-muted hover:text-foreground"}`}
|
|
130
121
|
>
|
|
131
|
-
|
|
122
|
+
stories
|
|
132
123
|
</button>
|
|
133
124
|
<button
|
|
134
125
|
onClick={() => setPage("dashboard")}
|
|
@@ -136,12 +127,6 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
|
|
|
136
127
|
>
|
|
137
128
|
dashboard
|
|
138
129
|
</button>
|
|
139
|
-
<button
|
|
140
|
-
onClick={() => setPage("llm-setup")}
|
|
141
|
-
className={`text-xs transition-colors ${page === "llm-setup" ? "text-accent" : "text-muted hover:text-foreground"}`}
|
|
142
|
-
>
|
|
143
|
-
llm
|
|
144
|
-
</button>
|
|
145
130
|
<button
|
|
146
131
|
onClick={() => setPage("settings")}
|
|
147
132
|
className={`text-xs transition-colors ${page === "settings" ? "text-accent" : "text-muted hover:text-foreground"}`}
|
|
@@ -156,62 +141,51 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
|
|
|
156
141
|
</header>
|
|
157
142
|
|
|
158
143
|
{/* Main content */}
|
|
159
|
-
<main className="flex-1
|
|
144
|
+
<main className="flex-1 min-h-0">
|
|
160
145
|
{page === "home" && (
|
|
161
|
-
<div className="mx-auto max-w-lg space-y-6 p-
|
|
162
|
-
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
</
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
146
|
+
<div className="mx-auto max-w-lg space-y-6 p-8">
|
|
147
|
+
<div className="text-center space-y-2">
|
|
148
|
+
<h1 className="text-2xl font-serif text-foreground">Write. Publish. Earn.</h1>
|
|
149
|
+
<p className="text-muted text-sm">
|
|
150
|
+
Claude CLI writes stories. You publish them on-chain.
|
|
151
|
+
</p>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div className="text-center space-y-3">
|
|
155
|
+
<button
|
|
156
|
+
onClick={() => setPage("stories")}
|
|
157
|
+
className="bg-accent text-white hover:bg-accent-dim px-6 py-2.5 rounded text-sm font-medium transition-colors"
|
|
158
|
+
>
|
|
159
|
+
Start Writing
|
|
160
|
+
</button>
|
|
161
|
+
{storyCount > 0 && (
|
|
162
|
+
<p className="text-muted text-xs">{storyCount} {storyCount === 1 ? "story" : "stories"} in progress</p>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div className="rounded border border-border p-4 space-y-2 text-xs text-muted">
|
|
167
|
+
<p className="font-medium text-foreground text-sm">How it works</p>
|
|
168
|
+
<ol className="space-y-1.5 list-decimal list-inside">
|
|
169
|
+
<li>Open the <strong>Stories</strong> tab — Claude CLI launches in the terminal</li>
|
|
170
|
+
<li>Tell Claude your story idea — it brainstorms, outlines, and writes</li>
|
|
171
|
+
<li>Review the live preview as Claude creates files</li>
|
|
172
|
+
<li>Click <strong>Publish</strong> to put your story on-chain</li>
|
|
173
|
+
<li>Earn 5% royalties on every trade at <a href="https://plotlink.xyz" target="_blank" rel="noopener noreferrer" className="text-accent underline">plotlink.xyz</a></li>
|
|
174
|
+
</ol>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<WalletCard token={token} />
|
|
190
178
|
</div>
|
|
191
179
|
)}
|
|
192
180
|
|
|
193
|
-
{page === "
|
|
194
|
-
<
|
|
195
|
-
)}
|
|
196
|
-
|
|
197
|
-
{page === "publish" && (
|
|
198
|
-
<Publish token={token} />
|
|
181
|
+
{page === "stories" && (
|
|
182
|
+
<StoriesPage token={token} authFetch={authFetch} />
|
|
199
183
|
)}
|
|
200
184
|
|
|
201
185
|
{page === "dashboard" && (
|
|
202
186
|
<Dashboard token={token} />
|
|
203
187
|
)}
|
|
204
188
|
|
|
205
|
-
{page === "llm-setup" && (
|
|
206
|
-
<LLMSetup
|
|
207
|
-
token={token}
|
|
208
|
-
onComplete={() => {
|
|
209
|
-
setLlmConfigured(true);
|
|
210
|
-
setPage("wallet-setup");
|
|
211
|
-
}}
|
|
212
|
-
/>
|
|
213
|
-
)}
|
|
214
|
-
|
|
215
189
|
{page === "wallet-setup" && (
|
|
216
190
|
<WalletSetupPage
|
|
217
191
|
token={token}
|
|
@@ -220,16 +194,9 @@ export function Layout({ token, onLogout }: { token: string; onLogout: () => voi
|
|
|
220
194
|
)}
|
|
221
195
|
|
|
222
196
|
{page === "settings" && (
|
|
223
|
-
<Settings token={token} onLogout={onLogout}
|
|
197
|
+
<Settings token={token} onLogout={onLogout} />
|
|
224
198
|
)}
|
|
225
199
|
</main>
|
|
226
|
-
|
|
227
|
-
{/* Footer */}
|
|
228
|
-
<footer className="border-border border-t px-4 py-2">
|
|
229
|
-
<p className="text-muted text-[10px]">
|
|
230
|
-
session active · localhost:7777
|
|
231
|
-
</p>
|
|
232
|
-
</footer>
|
|
233
200
|
</div>
|
|
234
201
|
);
|
|
235
202
|
}
|