diff --git a/AGENTS.md b/AGENTS.md
index 46c8383..45759fb 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -679,6 +679,27 @@ session with **no other memory of this project**. So:
`index.css` offsets `.leaflet-top.leaflet-left` by `4.25rem` **only under
`max-width:767px`** (desktop toolbar is in flow, no offset). Verified at
390×844: zoom top 135px vs search bottom 108px.
+- **Signup & onboarding UX pass (2026-10-01):** hardening the two highest-stakes
+ first impressions.
+ - **Signup is one fork.** `LoginArea` also rendered a `SignupMethodDialog`
+ ("Recovery Phrase vs Secret Key") + `MnemonicSignupDialog`, but
+ **`setSignupMethodDialogOpen(true)` existed nowhere** — the whole method
+ branch was DEAD code (unreachable). Removed the wiring (files kept on disk);
+ the misleading `nsecSignupDialogOpen` state was renamed `signupDialogOpen`.
+ `SignupDialog` (`welcome → profile → done`) is the only signup path.
+ - **`SignupDialog` gained a step indicator** (`STEP_ORDER`/`STEP_LABELS`:
+ Choose → Profile → Save key) and **makes backup a confirmation, not a skip**:
+ a checkbox ("I've saved my recovery code…") enables "Let's skate"; the
+ Download button also sets it. The old co-equal "Skip for now, skate" is now a
+ quiet text link ("I'll save it later from Settings"). Welcome copy no longer
+ claims "nothing to remember" (it contradicted step 3), the profile step warns
+ a signer may ask once, and non-Android copy points at signer apps instead of
+ implying a recommendation.
+ - **Onboarding adds "Where do you skate?"** (`OnboardingPage`): for a logged-in
+ skater with no cached fix, "Use my location" calls `getCurrentPosition()`
+ (which also `saveLastLocation()`s), so `MapPage`'s mount-time
+ `getLastLocation()` opens centered on their area. The logged-out wallet
+ section's dead disabled "Log in first" button became a plain line.
- **Empty-map nudge (2026-09-28):** `MapPage` distinguishes the two empties — the
existing "No spots match" card is for *filters* (`filteredSpots.length === 0 &&
allLocations.length > 0`) and a new "No spots mapped yet — be the first" card
diff --git a/src/components/auth/LoginArea.tsx b/src/components/auth/LoginArea.tsx
index f7b7dc3..5463fe9 100644
--- a/src/components/auth/LoginArea.tsx
+++ b/src/components/auth/LoginArea.tsx
@@ -6,8 +6,6 @@ import { User, UserPlus } from 'lucide-react';
import { Button } from '@/components/ui/button.tsx';
import LoginDialog from './LoginDialog';
import SignupDialog from './SignupDialog';
-import SignupMethodDialog from './SignupMethodDialog';
-import MnemonicSignupDialog from './MnemonicSignupDialog';
import { useLoggedInAccounts } from '@/hooks/useLoggedInAccounts';
import { AccountSwitcher } from './AccountSwitcher';
import { cn } from '@/lib/utils';
@@ -21,15 +19,11 @@ export interface LoginAreaProps {
export function LoginArea({ className, compact }: LoginAreaProps) {
const { currentUser } = useLoggedInAccounts();
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
- const [signupMethodDialogOpen, setSignupMethodDialogOpen] = useState(false);
- const [mnemonicSignupDialogOpen, setMnemonicSignupDialogOpen] = useState(false);
- const [nsecSignupDialogOpen, setNsecSignupDialogOpen] = useState(false);
+ const [signupDialogOpen, setSignupDialogOpen] = useState(false);
const handleLogin = () => {
setLoginDialogOpen(false);
- setSignupMethodDialogOpen(false);
- setMnemonicSignupDialogOpen(false);
- setNsecSignupDialogOpen(false);
+ setSignupDialogOpen(false);
};
return (
@@ -40,7 +34,7 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
// Compact mode for mobile headers - icon only
)}
@@ -658,28 +729,51 @@ const SignupDialog: React.FC = ({ isOpen, onClose, onComplete
- {/* Skate now — clearly available, intentionally quieter than "save".
- Copy adapts: once saved, it's a confident go; if not, it's an
- honest "you can do this later" so we never trap the user. */}
+ {/* Backup is a confirmation, not a skip. The primary action stays
+ locked until the key is saved (download sets it) or explicitly
+ acknowledged with the checkbox. The escape hatch remains — we
+ never trap anyone — but it's a quiet text link, never an equal
+ button competing with "save". */}
- No stress — we'll remind you, and you can save it anytime from Settings.
-
+
+ {!backupAck && (
+
+ I'll save it later from Settings
+
)}
+
Advanced: use a signer app instead
diff --git a/src/pages/OnboardingPage.tsx b/src/pages/OnboardingPage.tsx
index 32525e7..20711bf 100644
--- a/src/pages/OnboardingPage.tsx
+++ b/src/pages/OnboardingPage.tsx
@@ -7,6 +7,8 @@ import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCashu } from '@/hooks/wallet/useCashu';
import { useLocalStorage } from '@/hooks/useLocalStorage';
+import { getCurrentPosition } from '@/lib/geolocation';
+import { getLastLocation } from '@/lib/lastLocation';
import { LoginArea } from '@/components/auth/LoginArea';
import { WalletModal } from '@/components/WalletModal';
import { RelayEducation } from '@/components/onboarding/RelayEducation';
@@ -113,6 +115,32 @@ export default function OnboardingPage() {
// "remind me later" can never silently become "never" for this critical step.
const [backupConfirmed] = useLocalStorage('backup-confirmed', false);
+ // "Where do you skate?" — the single most useful first step. We reuse the
+ // same last-location cache MapPage reads on mount, so setting it here means
+ // the map opens on the skater's own area instead of the whole world, and no
+ // permission prompt surprises them at map load. `hasHome` just hides the
+ // card once a location exists.
+ const [locating, setLocating] = useState(false);
+ const [hasHome, setHasHome] = useState(() => !!getLastLocation());
+
+ const handleUseMyLocation = async () => {
+ setLocating(true);
+ try {
+ // getCurrentPosition() also persists the fix via saveLastLocation().
+ await getCurrentPosition();
+ setHasHome(true);
+ toast({ title: 'Home set 🛹', description: 'The map will now open on your area.' });
+ } catch {
+ toast({
+ title: "Couldn't get your location",
+ description: 'Check location permissions, or pick your area from the map.',
+ variant: 'destructive',
+ });
+ } finally {
+ setLocating(false);
+ }
+ };
+
const handleRelayEducationComplete = () => {
setShowRelayEducation(false);
setHasSeenRelayEducation(true);
@@ -235,6 +263,43 @@ export default function OnboardingPage() {
)}
+ {/* ── First run: where do you skate? ── */}
+ {user && !hasHome && (
+
+
+
+
+
+
+
Where do you skate?
+
+ Set your home area once and the map opens on your spots every time.
+ Your GPS never leaves your device.
+
+
+
+
+
+ {locating ? (
+
+ ) : (
+
+ )}
+ Use my location
+
+
+
+ Pick on the map
+
+
+