Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions example-apps/dashnote/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion example-apps/dashnote/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ function App() {
/>
)}
{tab === "how-it-works" && <HowItWorks />}
{tab === "settings" && <SettingsPanel />}
{tab === "settings" && (
<SettingsPanel onOpenLogin={() => setLoginOpen(true)} />
)}
</div>
</AppShell>

Expand Down
4 changes: 2 additions & 2 deletions example-apps/dashnote/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ export function AppShell({
closeDrawer();
}}
/>
{status !== "authenticated" && (
{status !== "authenticated" && status !== "browsing" && (
<NavButton
label="Login"
label="Sign in"
glyph="→"
active={false}
onClick={() => {
Expand Down
55 changes: 55 additions & 0 deletions example-apps/dashnote/src/components/DeleteNoteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Modal } from "./Modal";

export interface DeleteNoteModalProps {
open: boolean;
noteTitle: string;
deleting: boolean;
onCancel: () => void;
onConfirm: () => void;
}

export function DeleteNoteModal({
open,
noteTitle,
deleting,
onCancel,
onConfirm,
}: DeleteNoteModalProps) {
const trimmed = noteTitle.trim();
const subject = trimmed ? `“${trimmed}”` : "this note";

return (
<Modal
open={open}
title="Delete note"
onClose={() => {
if (!deleting) onCancel();
}}
footer={
<>
<button
type="button"
onClick={onConfirm}
disabled={deleting}
className="flex-1 rounded-md bg-[color:var(--color-danger)] px-4 py-2 text-[13px] font-semibold text-bg transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
>
{deleting ? "Deleting…" : "Delete"}
</button>
<button
type="button"
onClick={onCancel}
disabled={deleting}
className="rounded-md border border-line bg-transparent px-4 py-2 text-[13px] font-semibold text-ink-3 transition hover:border-line-2 hover:text-ink-2 disabled:cursor-not-allowed disabled:text-ink-4"
>
Cancel
</button>
</>
}
>
<p className="text-[13px] leading-6 text-ink-2">
Permanently delete {subject} from Dash Platform? This can&apos;t be
undone.
</p>
</Modal>
);
}
8 changes: 4 additions & 4 deletions example-apps/dashnote/src/components/IdentityCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function IdentityCard({
onClick={onLoginClick}
className="w-full rounded-md bg-accent px-3 py-2 text-[12px] font-semibold text-bg transition hover:bg-accent-dim"
>
Login
Sign in
</button>
<div className="mt-2.5 flex items-center gap-1.5">
<span
Expand All @@ -94,8 +94,8 @@ export function IdentityCard({
}

// Read-only mode has nothing to put in a menu (no identity → no Settings
// target, no Switch identity, no Log out), so the card goes straight to
// the login modal on click — matching the pre-menu behavior.
// target, no Login/Switch entry, no Log out), so the card goes straight
// to the login modal on click — matching the pre-menu behavior.
if (isReadonly) {
return (
<button
Expand Down Expand Up @@ -196,7 +196,7 @@ export function IdentityCard({
}}
className="rounded-md px-2 py-1.5 text-left text-[12px] font-medium text-ink-2 transition hover:bg-surface-2 hover:text-ink"
>
Switch identity
{isAuthed ? "Switch identity" : "Sign in"}
</button>
{isAuthed && (
<button
Expand Down
109 changes: 36 additions & 73 deletions example-apps/dashnote/src/components/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
const session = useSession();
const [secret, setSecret] = useState("");
const [identityIndex, setIdentityIndex] = useState("0");
const [contractInput, setContractInput] = useState(session.contractId ?? "");
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const [showAdvanced, setShowAdvanced] = useState(false);
Expand All @@ -37,10 +36,6 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
wifPreview.status === "wrong-purpose" ||
wifPreview.status === "key-disabled";

useEffect(() => {
setContractInput(session.contractId ?? "");
}, [session.contractId]);

useEffect(() => {
if (open) {
setRememberMe(true);
Expand All @@ -50,10 +45,6 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
}
}, [open]);

function applyContractId() {
session.setContractId(contractInput.trim() || null);
}

async function handleLogin(event: FormEvent) {
event.preventDefault();
setError(null);
Expand All @@ -74,7 +65,7 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
}

return (
<Modal open={open} onClose={onClose} title="Login">
<Modal open={open} onClose={onClose} title="Sign in">
<form onSubmit={handleLogin} className="flex flex-col gap-4 py-2">
{showRememberedPanel && session.rememberedIdentityId && (
<label
Expand Down Expand Up @@ -187,26 +178,17 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
)}
</label>

{session.rememberedIdentityId && (
{session.rememberedIdentityId && !useDifferentIdentity && (
<div
data-testid="remembered-identity-actions"
className="flex flex-wrap gap-3 text-[11px]"
>
{!useDifferentIdentity && (
<button
type="button"
onClick={() => setUseDifferentIdentity(true)}
className="font-medium text-accent-dim underline-offset-2 hover:text-accent hover:underline"
>
Use a different identity
</button>
)}
<button
type="button"
onClick={() => session.forgetIdentity()}
className="font-medium text-ink-3 underline-offset-2 hover:text-[color:var(--color-danger)] hover:underline"
onClick={() => setUseDifferentIdentity(true)}
className="font-medium text-accent-dim underline-offset-2 hover:text-accent hover:underline"
>
Forget this device
Use a different identity
</button>
</div>
)}
Expand All @@ -221,57 +203,38 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
<span>Remember this identity on this device</span>
</label>

<button
type="button"
onClick={() => setShowAdvanced(!showAdvanced)}
className="flex items-center gap-1 self-start text-[11px] font-medium text-ink-3 transition hover:text-ink"
>
<span
className={`inline-block transition-transform ${showAdvanced ? "rotate-90" : ""}`}
>
</span>
Advanced settings
</button>
{!isWifInput && (
<>
<button
type="button"
onClick={() => setShowAdvanced(!showAdvanced)}
className="flex items-center gap-1 self-start text-[11px] font-medium text-ink-3 transition hover:text-ink"
>
<span
className={`inline-block transition-transform ${showAdvanced ? "rotate-90" : ""}`}
>
</span>
Advanced settings
</button>

{showAdvanced && (
<div className="flex flex-col gap-4 rounded-md border border-line bg-bg/40 p-3">
{!isWifInput && (
<label className="flex flex-col gap-1">
<span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-ink-4">
Identity index
</span>
<input
type="number"
min={0}
value={identityIndex}
onChange={(event) => setIdentityIndex(event.target.value)}
className="w-24 rounded-md border border-line bg-bg px-3 py-2 text-[13px] text-ink outline-none transition focus:border-accent-dim"
/>
</label>
{showAdvanced && (
<div className="flex flex-col gap-4 rounded-md border border-line bg-bg/40 p-3">
<label className="flex flex-col gap-1">
<span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-ink-4">
Identity index
</span>
<input
type="number"
min={0}
value={identityIndex}
onChange={(event) => setIdentityIndex(event.target.value)}
className="w-24 rounded-md border border-line bg-bg px-3 py-2 text-[13px] text-ink outline-none transition focus:border-accent-dim"
/>
</label>
</div>
)}

<div className="flex flex-col gap-2">
<span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-ink-4">
Contract ID (optional)
</span>
<input
type="text"
value={contractInput}
onChange={(event) => setContractInput(event.target.value)}
placeholder="Paste a Dashnote note contract ID to reuse"
className="rounded-md border border-line bg-bg px-3 py-2 font-mono text-[12px] text-ink outline-none transition focus:border-accent-dim"
/>
<button
type="button"
onClick={applyContractId}
disabled={contractInput.trim() === (session.contractId ?? "")}
className="self-start rounded-md border border-line-2 bg-transparent px-3 py-1 text-[12px] font-semibold text-ink-2 transition hover:border-accent-dim hover:text-ink disabled:cursor-not-allowed disabled:border-line disabled:text-ink-4"
>
Use this ID
</button>
</div>
</div>
</>
)}

{error && (
Expand All @@ -291,7 +254,7 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
disabled={submitting || !secret.trim() || previewBlocksLogin}
className="flex-1 rounded-md bg-accent px-4 py-2 text-[13px] font-semibold text-bg transition hover:bg-accent-dim disabled:cursor-not-allowed disabled:bg-surface-2 disabled:text-ink-4"
>
{submitting ? "Connecting…" : "Login"}
{submitting ? "Connecting…" : "Sign in"}
</button>
<button
type="button"
Expand Down
25 changes: 20 additions & 5 deletions example-apps/dashnote/src/components/NotesWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createNote } from "../dash/createNote";
import { deleteNote } from "../dash/deleteNote";
import { getNote, listMyNotes, type NoteRecord } from "../dash/queries";
import { updateNote } from "../dash/updateNote";
import { DeleteNoteModal } from "./DeleteNoteModal";
import { useMediaQuery } from "../hooks/useMediaQuery";
import { byteLength, FIELD_BYTE_LIMIT } from "../lib/fieldLimits";
import { errorMessage } from "../lib/logger";
Expand Down Expand Up @@ -71,6 +72,7 @@ export function NotesWorkspace({
const [detailLoading, setDetailLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [deleting, setDeleting] = useState(false);
const [deleteRequested, setDeleteRequested] = useState(false);
const [error, setError] = useState<string | null>(null);
const [revalidating, setRevalidating] = useState(false);
const [editsReady, setEditsReady] = useState(false);
Expand Down Expand Up @@ -545,13 +547,18 @@ export function NotesWorkspace({
}
}

async function handleDelete() {
function requestDelete() {
if (!sdk || !keyManager || !contractId || !isAuthed || !selectedId) return;
if (selectedId === "new") {
resetDraft();
return;
}
if (!window.confirm("Delete this note permanently?")) return;
setDeleteRequested(true);
}

async function confirmDelete() {
if (!sdk || !keyManager || !contractId || !isAuthed || !selectedId) return;
if (selectedId === "new") return;

setDeleting(true);
setError(null);
Expand All @@ -564,6 +571,7 @@ export function NotesWorkspace({
noteId: selectedId,
log,
});
setDeleteRequested(false);
await reloadNotes();
} catch (err) {
setError(errorMessage(err));
Expand Down Expand Up @@ -594,8 +602,8 @@ export function NotesWorkspace({
</svg>
}
title="Sign in to see your notes"
description="Dashnote stores notes against your testnet identity. Log in with a Dash Platform identity to create, edit, and review your notes."
actionLabel="Log in"
description="Dashnote stores notes against your testnet identity. Sign in with a Dash Platform identity to create, edit, and review your notes."
actionLabel="Sign in"
onAction={onOpenLogin}
secondaryHref="https://bridge.thepasta.org/"
secondaryLabel="Need an identity? Create one on Dash Bridge"
Expand Down Expand Up @@ -652,7 +660,7 @@ export function NotesWorkspace({
onTitleChange={setTitle}
onMessageChange={setMessage}
onSave={() => void handleSave()}
onDelete={() => void handleDelete()}
onDelete={requestDelete}
onBack={handleBack}
loading={detailLoading}
saving={saving}
Expand All @@ -673,6 +681,13 @@ export function NotesWorkspace({
</div>
</div>
)}
<DeleteNoteModal
open={deleteRequested}
noteTitle={title}
deleting={deleting}
onCancel={() => setDeleteRequested(false)}
onConfirm={() => void confirmDelete()}
/>
</div>
);
}
Expand Down
19 changes: 16 additions & 3 deletions example-apps/dashnote/src/components/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ function Section({
);
}

export function SettingsPanel() {
interface SettingsPanelProps {
onOpenLogin: () => void;
}

export function SettingsPanel({ onOpenLogin }: SettingsPanelProps) {
const session = useSession();
const {
register,
Expand Down Expand Up @@ -108,8 +112,17 @@ export function SettingsPanel() {

if (!isConnected) {
return (
<div className="rounded-md border border-line bg-bg/40 p-6 text-center text-[13px] text-ink-3">
Sign in to view and manage your identity, contract, and device data.
<div className="flex flex-col items-center gap-4 rounded-md border border-line bg-bg/40 p-6 text-center text-[13px] text-ink-3">
<p>
Sign in to view and manage your identity, contract, and device data.
</p>
<button
type="button"
onClick={onOpenLogin}
className="rounded-full bg-accent px-5 py-2 text-[13px] font-semibold text-bg transition hover:bg-accent-dim"
>
Sign in
</button>
</div>
);
}
Expand Down
Loading