Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
936a519
feat(session): store password for account backup
escapedcat Apr 11, 2026
16821d0
feat(nav): add UserMenu dropdown replacing bookmark icon
escapedcat Apr 11, 2026
c069b07
fix: add password backfill test and aria-haspopup on UserMenu
escapedcat Apr 11, 2026
201da3d
feat(nav): show UserMenu always with login/logout states
escapedcat Apr 11, 2026
c15c603
refactor(nav): remove logout, users always have an account
escapedcat Apr 11, 2026
75f4ae3
feat(auth): add login page and server route
escapedcat Apr 11, 2026
8045aeb
feat(nav): add "Switch account" to logged-in user menu
escapedcat Apr 11, 2026
6a6f945
feat(nav): show username as tooltip on account icon
escapedcat Apr 11, 2026
b1df7bc
feat(save): show toast on first account creation
escapedcat Apr 11, 2026
bb11dc6
fix(nav): match user icon button size to theme toggle
escapedcat Apr 11, 2026
e62dfd8
fix(session): hydrate session immediately instead of in onMount
escapedcat Apr 11, 2026
f78a7df
Revert "fix(session): hydrate session immediately instead of in onMount"
escapedcat Apr 11, 2026
e4a5328
fix: confirm before switch account, remove dead logout key
escapedcat Apr 11, 2026
66986a6
feat(auth): add BackupModal and remove switch account confirm
escapedcat Apr 11, 2026
d6497b7
feat(auth): only show backup for auto-generated accounts
escapedcat Apr 11, 2026
3c4d8dd
fix(auth): don't store password in localStorage for known accounts
escapedcat Apr 11, 2026
0860faf
fix(saved): show empty state instead of redirecting to /map
escapedcat Apr 11, 2026
64364bf
fix(saved): redirect to /login instead of showing empty state
escapedcat Apr 11, 2026
766371c
fix(auth): add input length validation on login server route
escapedcat Apr 11, 2026
b81fc87
feat(login): add link to developer portal for account creation
escapedcat Apr 11, 2026
de4aa68
fix(backup): add Escape key handling, remove deprecated copy fallback
escapedcat Apr 11, 2026
9beafbc
fix(nav): replace "Switch account" with "Log out"
escapedcat Apr 11, 2026
a9abb2c
fix(nav): use unique IDs for UserMenu trigger buttons
escapedcat Apr 11, 2026
e69ea5a
fix(backup): handle clipboard errors and use i18n for copy toast
escapedcat Apr 11, 2026
1d6d4b2
docs(session): clarify password storage differs by account type
escapedcat Apr 11, 2026
a20c35e
fix(auth): don't trim passwords, sanitize error logs
escapedcat Apr 11, 2026
fcd4e93
fix(nav): add type=button to UserMenu action buttons
escapedcat Apr 11, 2026
074f7e0
fix(backup): replace a11y ignores with role=presentation, add type=bu…
escapedcat Apr 11, 2026
1d357ca
fix(nav): use deterministic IDs for UserMenu instances
escapedcat Apr 11, 2026
5b57045
refactor(routes): move /saved to /user/saved
escapedcat Apr 11, 2026
1334b2d
style(icon): use bookmark with checkmark for saved state
escapedcat Apr 12, 2026
8b3b806
chore: add nostr-tools for NIP-07/nsec signing
escapedcat Apr 13, 2026
2101556
feat(auth): add /api/session/nostr proxy route for Nostr sign-in
escapedcat Apr 13, 2026
39ca6da
feat(auth): add NIP-07 Nostr extension sign-in on /login #903
escapedcat Apr 13, 2026
4f06ec3
feat(auth): add nsec paste fallback for Nostr sign-in on mobile #903
escapedcat Apr 13, 2026
98b1438
refactor(session): extract loadSavedItemsFromServer helper #903
escapedcat Apr 13, 2026
77e919f
fix(auth): classify nsec decode vs transport errors separately #903
escapedcat Apr 13, 2026
f131db3
fix(auth): clear Nostr extension-detect timeout on unmount #903
escapedcat Apr 13, 2026
bb4da85
refactor(auth): share NOSTR_AUTH_URL between client and server route …
escapedcat Apr 13, 2026
a8f8649
fix(auth): cap /api/session/nostr request body to 4 KB #903
escapedcat Apr 13, 2026
4850c61
fix(auth): use autocomplete=new-password on nsec input #903
escapedcat Apr 13, 2026
63e48c9
feat(auth): parameterize NOSTR_AUTH_URL via VITE_API_BASE_URL #903
escapedcat May 10, 2026
50fa521
refactor(auth): use canonical NIP-98 Authorization header to API #903
escapedcat May 10, 2026
f6c6d5d
refactor(api): route all SvelteKit session proxies through \$lib/api-…
escapedcat May 10, 2026
dbd18e9
fix(nav): redirect to /login after logout #903
escapedcat May 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"localforage": "^1.10.0",
"maplibre-gl": "^4.7.1",
"marked": "^18.0.0",
"nostr-tools": "^2.23.3",
"opening_hours": "^3.12.0",
"qrcode": "^1.5.4",
"svelte-i18n": "^4.0.1",
Expand Down
104 changes: 88 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const materialExceptions: Record<string, string> = {
currency_bitcoin: "material-symbols:currency-bitcoin",
close_round: "ic:round-close",
my_location: "material-symbols:my-location-rounded",
bookmark_filled: "ic:baseline-bookmark",
bookmark_filled: "ic:baseline-bookmark-added",
account_circle_filled: "ic:baseline-account-circle",
};

const faBrandIcons = ["x-twitter", "instagram", "facebook", "twitter"];
Expand Down
3 changes: 2 additions & 1 deletion src/components/SaveButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Icon from "$components/Icon.svelte";
import api from "$lib/axios";
import { _ } from "$lib/i18n";
import { session } from "$lib/session";
import { errToast } from "$lib/utils";
import { errToast, successToast } from "$lib/utils";

// The numeric ID of the item to save/unsave.
export let id: number;
Expand Down Expand Up @@ -56,6 +56,7 @@ async function toggle() {
let current = previousSession;
if (!current) {
current = await session.signUp();
successToast($_("save.accountCreated"));
}

const nextSaved =
Expand Down
115 changes: 115 additions & 0 deletions src/components/auth/BackupModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";

import Icon from "$components/Icon.svelte";
import { _ } from "$lib/i18n";
import { session } from "$lib/session";
import { successToast } from "$lib/utils";

const dispatch = createEventDispatcher();

let showPassword = false;

async function copyToClipboard(text: string) {
try {
await navigator.clipboard.writeText(text);
successToast($_("backup.copied"));
} catch (err) {
console.error("Clipboard write failed", err);
}
}
</script>

<svelte:window on:keydown={(e) => e.key === "Escape" && dispatch("close")} />

<div
role="presentation"
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/50"
on:click|self={() => dispatch("close")}
>
<div class="mx-4 w-full max-w-sm rounded-xl bg-white p-6 shadow-xl dark:bg-dark">
<div class="mb-4 flex items-center justify-between">
<h2 class="text-xl font-semibold text-primary dark:text-white">
{$_("backup.title")}
</h2>
<button
type="button"
on:click={() => dispatch("close")}
class="text-body transition-colors hover:text-primary dark:text-white/50 dark:hover:text-white"
>
<Icon type="material" icon="close" w="20" h="20" />
</button>
</div>

<p class="mb-4 text-sm text-body dark:text-white/70">
{$_("backup.description")}
</p>

{#if $session}
<div class="space-y-3">
<div>
<label for="backup-username" class="mb-1 block text-xs font-semibold text-body dark:text-white/70">
{$_("backup.username")}
</label>
<div class="flex items-center gap-2">
<input
id="backup-username"
type="text"
readonly
value={$session.username}
class="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-sm text-primary dark:border-white/20 dark:bg-white/5 dark:text-white"
/>
<button
on:click={() => copyToClipboard($session?.username ?? "")}
class="shrink-0 rounded-lg border border-gray-300 p-2 transition-colors hover:bg-gray-100 dark:border-white/20 dark:hover:bg-white/10"
title={$_("backup.copy")}
>
<Icon type="material" icon="content_copy" w="16" h="16" class="text-primary dark:text-white" />
</button>
</div>
</div>

<div>
<label for="backup-password" class="mb-1 block text-xs font-semibold text-body dark:text-white/70">
{$_("backup.password")}
</label>
<div class="flex items-center gap-2">
<input
id="backup-password"
type={showPassword ? "text" : "password"}
readonly
value={$session.password || $_("backup.unavailable")}
class="w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-sm text-primary dark:border-white/20 dark:bg-white/5 dark:text-white"
/>
<button
on:click={() => (showPassword = !showPassword)}
class="shrink-0 rounded-lg border border-gray-300 p-2 transition-colors hover:bg-gray-100 dark:border-white/20 dark:hover:bg-white/10"
title={showPassword ? $_("backup.hide") : $_("backup.show")}
>
<Icon
type="material"
icon={showPassword ? "visibility_off" : "visibility"}
w="16"
h="16"
class="text-primary dark:text-white"
/>
</button>
{#if $session.password}
<button
on:click={() => copyToClipboard($session?.password ?? "")}
class="shrink-0 rounded-lg border border-gray-300 p-2 transition-colors hover:bg-gray-100 dark:border-white/20 dark:hover:bg-white/10"
title={$_("backup.copy")}
>
<Icon type="material" icon="content_copy" w="16" h="16" class="text-primary dark:text-white" />
</button>
{/if}
</div>
</div>
</div>

<p class="mt-4 text-xs text-body dark:text-white/50">
{$_("backup.warning")}
</p>
{/if}
</div>
</div>
Loading