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
103 changes: 103 additions & 0 deletions src/components/Walkthrough.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { useEffect, useRef, useState } from 'react';

export const WALK_STORAGE_KEY = 'nem-walk-seen';

interface WalkthroughProps {
onClose: () => void;
}

const steps = [
{
label: 'Step 01 / 03',
title: 'Point it at a relay',
render: () => (
<>
Nostr is a network of relays — public servers that hold events. Add at least one{' '}
<code>wss://</code> URL to query. You can add several and this tool will query them in parallel.
</>
),
},
{
label: 'Step 02 / 03',
title: 'Filter by Kind or NIP',
render: () => (
<>
Each event has a numeric <code>kind</code>. If you know it, use <strong>Kind</strong>. If not,
search by <strong>NIP</strong> — the spec number — and we'll resolve its kinds automatically.
Then narrow with author, tags, or time range.
</>
),
},
{
label: 'Step 03 / 03',
title: 'Search vs Stream',
render: () => (
<>
<strong>Search</strong> fetches a snapshot (past events, bounded by <code>limit</code>).{' '}
<strong>Stream</strong> keeps a live socket open and prints new events as they land.
Use Stream to debug relays or watch a kind in real time.
</>
),
},
];

export function Walkthrough({ onClose }: WalkthroughProps) {
const [step, setStep] = useState(0);
const s = steps[step];
const isLast = step === steps.length - 1;
const primaryBtnRef = useRef<HTMLButtonElement>(null);

useEffect(() => {
const previouslyFocused = document.activeElement as HTMLElement | null;
primaryBtnRef.current?.focus();

const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', onKey);
return () => {
document.removeEventListener('keydown', onKey);
previouslyFocused?.focus?.();
};
}, [onClose]);

return (
<div
className="walk-overlay"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-labelledby="walk-title"
>
<div className="walk-card" onClick={(e) => e.stopPropagation()}>
<div className="walk-step">{s.label}</div>
<h2 id="walk-title" className="walk-title">{s.title}</h2>
<p className="walk-desc">{s.render()}</p>
<div className="walk-nav">
<div className="walk-dots">
{steps.map((_, i) => (
<span key={i} className={`walk-dot ${i <= step ? 'on' : ''}`} />
))}
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<button
type="button"
onClick={onClose}
className="h-8 px-3 text-xs bg-accent/10 border border-accent/30 hover:bg-accent/20 rounded"
>
Skip
</button>
<button
ref={primaryBtnRef}
type="button"
onClick={() => (isLast ? onClose() : setStep(step + 1))}
className="h-8 px-4 text-xs bg-accent/80 hover:bg-accent border-accent/50 rounded"
>
{isLast ? 'Start monitoring' : 'Next'}
</button>
</div>
</div>
</div>
</div>
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
21 changes: 21 additions & 0 deletions src/data/presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const SUGGESTED_RELAYS: string[] = [
'relay.damus.io',
'relay.primal.net',
'nos.lol',
'relay.nostr.band',
'relay.mostro.network',
];

export interface QueryPreset {
id: string;
title: string;
desc: string;
kind: string;
}

export const PRESETS: QueryPreset[] = [
{ id: 'notes', title: 'Recent notes', desc: 'kind:1 · short text notes', kind: '1' },
{ id: 'mostro', title: 'Mostro P2P orders', desc: 'kind:38383 · NIP-69', kind: '38383' },
{ id: 'zaps', title: 'Zap receipts', desc: 'kind:9735 · NIP-57', kind: '9735' },
{ id: 'profiles', title: 'User metadata', desc: 'kind:0 · profile updates', kind: '0' },
];
Loading
Loading