chore: added restore from backup to homepage 02bc5284
Steve · 2025-11-04 23:39 2 file(s) · +76 −21
src/App.tsx +65 −0
15 15
	extractPostContent,
16 16
	extractPostDate,
17 17
} from "@/lib/feed-operations";
18 +
import {
19 +
	Dialog,
20 +
	DialogContent,
21 +
	DialogDescription,
22 +
	DialogHeader,
23 +
	DialogTitle,
24 +
} from "@/components/ui/dialog";
25 +
import { Upload } from "lucide-react";
26 +
import { Mnemonic } from "@evolu/common";
18 27
19 28
function App() {
20 29
	const allFeeds = useQuery(allFeedsQuery);
22 31
	const [urlInput, setUrlInput] = React.useState("");
23 32
	const [isAddingFeed, setIsAddingFeed] = React.useState(false);
24 33
	const [errorMessage, setErrorMessage] = React.useState("");
34 +
	const [isRestoreDialogOpen, setIsRestoreDialogOpen] = React.useState(false);
35 +
	const [restoreMnemonic, setRestoreMnemonic] = React.useState("");
25 36
26 37
	const evolu = useEvolu();
38 +
39 +
	function handleRestoreDialogOpenChange(open: boolean) {
40 +
		setIsRestoreDialogOpen(open);
41 +
		if (!open) {
42 +
			setRestoreMnemonic("");
43 +
		}
44 +
	}
45 +
46 +
	function handleRestore() {
47 +
		if (restoreMnemonic.trim()) {
48 +
			evolu.restoreAppOwner(restoreMnemonic as Mnemonic);
49 +
			setIsRestoreDialogOpen(false);
50 +
			setRestoreMnemonic("");
51 +
			toast.success("Account restored successfully");
52 +
		}
53 +
	}
27 54
28 55
	async function addFeed() {
29 56
		if (!urlInput.trim()) {
132 159
								{errorMessage}
133 160
							</div>
134 161
						)}
162 +
						<Button
163 +
							variant="outline"
164 +
							onClick={() => setIsRestoreDialogOpen(true)}
165 +
							className="w-full"
166 +
						>
167 +
							<Upload className="h-4 w-4 mr-2" />
168 +
							Restore from Backup
169 +
						</Button>
135 170
					</div>
136 171
				</div>
137 172
			)}
173 +
			<Dialog
174 +
				open={isRestoreDialogOpen}
175 +
				onOpenChange={handleRestoreDialogOpenChange}
176 +
			>
177 +
				<DialogContent>
178 +
					<DialogHeader>
179 +
						<DialogTitle>Restore from Backup</DialogTitle>
180 +
						<DialogDescription>
181 +
							Enter your backup phrase to restore your account and access your
182 +
							encrypted data.
183 +
						</DialogDescription>
184 +
					</DialogHeader>
185 +
					<div className="space-y-4">
186 +
						<textarea
187 +
							className="w-full p-4 bg-muted rounded-lg font-mono text-sm resize-none min-h-[100px]"
188 +
							placeholder="Enter your backup phrase here..."
189 +
							value={restoreMnemonic}
190 +
							onChange={(e) => setRestoreMnemonic(e.target.value)}
191 +
						/>
192 +
						<Button
193 +
							onClick={handleRestore}
194 +
							disabled={!restoreMnemonic.trim()}
195 +
							className="w-full"
196 +
						>
197 +
							<Upload className="h-4 w-4 mr-2" />
198 +
							Restore Account
199 +
						</Button>
200 +
					</div>
201 +
				</DialogContent>
202 +
			</Dialog>
138 203
		</main>
139 204
	);
140 205
}
src/lib/evolu.ts +11 −21
1 -
import * as Evolu from "@evolu/common";
2 1
import { evoluReactWebDeps } from "@evolu/react-web";
3 2
import { Schema, type RSSFeedId } from "./scheme.ts";
4 3
import { createUseEvolu } from "@evolu/react";
5 -
6 -
const service = "alcove";
7 -
8 -
// Initialize authentication
9 -
const authResult = await evoluReactWebDeps.localAuth.login(undefined, {
10 -
	service,
11 -
});
4 +
import { createEvolu, SimpleName, sqliteTrue } from "@evolu/common";
12 5
13 -
export const evolu = Evolu.createEvolu(evoluReactWebDeps)(Schema, {
14 -
	name: Evolu.SimpleName.orThrow(
15 -
		`${service}-${authResult?.owner?.id ?? "guest"}`,
16 -
	),
6 +
export const evolu = createEvolu(evoluReactWebDeps)(Schema, {
7 +
	name: SimpleName.orThrow("alcove"),
17 8
	reloadUrl: "/",
18 -
	encryptionKey: authResult?.owner?.encryptionKey,
19 -
	externalAppOwner: authResult?.owner,
20 9
	transports: [
21 10
		{
22 11
			type: "WebSocket",
23 12
			url: "wss://relay.alcove.tools",
24 13
		},
14 +
		// {
15 +
		// 	type: "WebSocket",
16 +
		// 	url: "ws://localhost:4000",
17 +
		// },
25 18
	],
26 19
});
27 20
38 31
});
39 32
40 33
export const allFeedsQuery = evolu.createQuery((db) =>
41 -
	db
42 -
		.selectFrom("rssFeed")
43 -
		.selectAll()
44 -
		.where("isDeleted", "is not", Evolu.sqliteTrue),
34 +
	db.selectFrom("rssFeed").selectAll().where("isDeleted", "is not", sqliteTrue),
45 35
);
46 36
47 37
export const postsByFeedQuery = (feedId: string) =>
50 40
			.selectFrom("rssPost")
51 41
			.selectAll()
52 42
			.where("feedId", "=", feedId as RSSFeedId)
53 -
			.where("isDeleted", "is not", Evolu.sqliteTrue)
43 +
			.where("isDeleted", "is not", sqliteTrue)
54 44
			.orderBy("id", "desc"),
55 45
	);
56 46
58 48
	db
59 49
		.selectFrom("rssPost")
60 50
		.selectAll()
61 -
		.where("isDeleted", "is not", Evolu.sqliteTrue)
51 +
		.where("isDeleted", "is not", sqliteTrue)
62 52
		.orderBy("id", "desc"),
63 53
);
64 54
66 56
	db
67 57
		.selectFrom("rssFeed")
68 58
		.selectAll()
69 -
		.where("isDeleted", "is not", Evolu.sqliteTrue)
59 +
		.where("isDeleted", "is not", sqliteTrue)
70 60
		.orderBy("category", "asc"),
71 61
);
72 62