Skip to main content

Overview

Your Profile page is mission control for account management, achievement tracking, and privacy settings. Customize your identity, link multiple auth methods, and monitor your journaling progress.
Four Main Tabs: Overview (goals + weekly reflection), Achievements, Activity (heatmap + history), Settings (profile + accounts + vault).

Profile Sidebar

User Information (app/profile/ProfilePageClient.tsx:545-652)

Profile Card

Displays:
  • Avatar (from Google or uploaded image)
  • Display name
  • Bio (short description)
  • Location (optional)
  • Website link (optional)
  • Wallet address (truncated: 0x1234…5678)
  • ETH balance (from Wagmi)
  • Member since date
Click Edit Profile to update any of these fields in Settings tab.

Quick Stats

Your Impact:
  • 📖 Total Stories
  • ❤️ Total Likes
  • 👁️ Total Views
  • 🪙 Books Created
Stats update in real-time. Calculated on page load from Supabase queries.

Overview Tab

Writing Goals (app/profile/ProfilePageClient.tsx:690-722)

1

Daily Writing Streak

Tracks consecutive days with at least one story saved.Algorithm (ProfilePageClient.tsx:293-317):
  1. Map all stories to dates (yyyy-mm-dd format)
  2. Starting today, check each previous day
  3. Increment counter if day has stories
  4. Stop at first day with no activity
Streak Logic:
  • Counts backward from today
  • Allows “grace day” if today just started (haven’t posted yet)
  • Resets to 0 if you miss a day
Aim for 7-day streak to unlock “Consistent Writer” achievement.
2

Monthly Stories Goal

Progress toward 10 stories per month.Calculation:
  • Filters stories by current month + year
  • Shows as “X/10 stories”
  • Progress bar fills proportionally
Month is based on created_at (when you saved), not story_date.
3

Community Engagement

Tracks total likes received across all public stories.Target: 100 likes
Make stories public and share on social feed to earn likes faster.

Weekly Reflection (components/WeeklyReflection.tsx)

Automatic Insights:At the end of each week (Sunday), iStory generates:
  • Summary of themes from week’s stories
  • Emotional tone analysis
  • Key moments highlighted
  • Suggestions for next week
How to View:
  • Appears in Profile Overview tab
  • Also accessible from Library “Weekly Reflections” section
  • Saved to weekly_reflections table
Requires at least 3 stories in past week to generate reflection.

Daily Journal Compilation (app/profile/ProfilePageClient.tsx:444-496)

If you recorded 2+ stories today:A special card appears:
  • “Daily Recap Available”
  • Shows count of today’s stories
  • Compile Daily Journal button
What it does:
  1. Bundles all today’s stories
  2. Creates IPFS metadata with date-specific title
  3. Mints NFT book (0.001 ETH fee)
  4. Saves to books table
  5. Updates book count stat
Great for daily journaling habit—creates one book per day.

Achievements Tab

Achievement System (app/profile/ProfilePageClient.tsx:360-368)

First Story

Recorded your first journal entry.Unlocked: After saving 1 story.

Prolific Writer

Recorded 10+ stories.Unlocked: After saving 10 stories.

Community Star

Received 50+ total likes.Unlocked: When sum of all story likes ≥ 50.

Book Author

Compiled your first digital book.Unlocked: After minting 1 NFT book.

Daily Grinder

Wrote 3 stories in one day.Unlocked: When any single day has ≥ 3 stories.

Vocalist

Recorded a story with audio.Unlocked: After saving story with has_audio: true.
Visual States:
  • 🎖️ Earned: Green background, checkmark, date shown
  • Locked: Gray background, reduced opacity

Activity Tab

Contribution Heatmap (app/profile/ProfilePageClient.tsx:769-841)

How it works:Grid Layout:
  • Last 52 weeks displayed
  • 7 rows (Sun-Sat) × ~52 columns
  • Each cell = one day
  • Aligned to Sunday as week start
Color Scale:
  • ⬜ Gray: No stories (0 count)
  • 🟢 Light Green: 1 story
  • 🟢 Medium Green: 2-3 stories
  • 🟢 Dark Green: 4+ stories
Data Source:
  • Stories mapped to dates via created_at
  • Counts aggregated by date
  • Hover shows date + story count tooltip
Consistent green squares = strong writing habit!

Recent Activity List (app/profile/ProfilePageClient.tsx:843-881)

Per-Day Statistics:For each active day in past 2 weeks:
  • Date (e.g., “Monday, Mar 4”)
  • 📖 Entry count (stories written)
  • ❤️ Likes received (sum across day’s stories)
Sorting: Newest day first (descending)
Only shows days with activity. Inactive days are hidden.

Settings Tab

Profile Settings Form (app/profile/ProfilePageClient.tsx:887-924)

1

Edit Profile Fields

Editable Information:
  • Display Name: Your public name
  • Username: Unique handle (alphanumeric + underscores)
  • Bio: Short description (280 chars max recommended)
  • Location: City/country (optional)
  • Website: Personal site or social link
Changes save to users table via Supabase update query.
2

Save Changes

Click Save Changes button:What happens:
  1. Form validation (no empty required fields)
  2. Supabase update: users.update(formData).eq('id', authInfo.id)
  3. Success toast: “Profile updated!”
  4. Auto-switch to Overview tab
Username must be unique. Save fails if username already taken.
3

Email Notification (Optional)

If email changed and notifications enabled:Welcome Email Sent:
  • Uses Resend API
  • Confirms profile setup
  • Includes quick start guide
Email service: emailService.sendWelcomeEmail() (app/utils/emailService.ts).

Linked Accounts (app/profile/ProfilePageClient.tsx:926-1027)

Linking Status:
Account TypeCheckIndicator
GoogleauthInfo?.google_id exists✅ Linked (email) / ⬜ Not linked
WalletauthInfo?.wallet_address exists✅ Linked (0x123…456) / ⬜ Not linked
Link Wallet (Google → Wallet):
  1. Sign in with Google
  2. Click Link Wallet in Settings
  3. If no wallet connected, RainbowKit modal opens
  4. After connection, sign message to prove ownership
  5. Server verifies signature via /api/auth/link-account
  6. Profile updated: auth_provider: 'both'
Link Google (Wallet → Google):
  1. Sign in with wallet
  2. Click Link Google in Settings
  3. Sign message to get secure linking token
  4. OAuth flow initiates (stored in sessionStorage)
  5. After OAuth, AuthProvider matches token + completes link
  6. Profile updated with google_id + auth_provider: 'both'
Security: Linking requires cryptographic signature to prevent account hijacking.

Vault Encryption

Local Vault Settings (components/vault/VaultSettings.tsx)

1

What is the Vault?

Client-side encryption layer:
  • Stories encrypted with AES-256-GCM before saving to IndexedDB
  • PIN never leaves your device
  • DEK (Data Encryption Key) derived from PIN via PBKDF2 (100,000 iterations)
  • KEK (Key Encryption Key) wraps DEK using AES-KW
  • Decryption only possible when vault unlocked
Architecture: PIN → PBKDF2 → KEK → wraps DEK → encrypts content.
2

Set Up Vault

Initial Setup:
  1. Navigate to Profile → Settings → Local Vault
  2. Click Set Up Vault
  3. Create 6-digit PIN
  4. Confirm PIN
  5. Vault initialized (DEK generated, wrapped with PIN-derived KEK)
  6. Stored in IndexedDB: vault.vault_keys table
If you forget your PIN, encrypted data is unrecoverable. No backdoor exists.
3

Unlock Vault

Before accessing encrypted stories:
  1. Click Unlock Vault
  2. Enter 6-digit PIN
  3. KEK derived from PIN + stored salt
  4. DEK unwrapped with KEK
  5. DEK held in memory for session
  6. Encrypted stories decrypted on-demand
Vault auto-locks on sign-out. DEKs cleared from memory via clearAllKeys().
4

Change PIN

Re-encrypt DEK with new PIN:
  1. Vault must be unlocked
  2. Click Change PIN
  3. Enter current PIN (verification)
  4. Enter new PIN
  5. Confirm new PIN
  6. New KEK derived from new PIN
  7. DEK re-wrapped with new KEK
  8. Updated in vault_keys table
Changing PIN does not re-encrypt stories—only re-wraps the DEK.

Vault Integration (lib/vault/)

Save Flow (with vault enabled):
  1. User saves story via /record
  2. Story saves to cloud (Supabase + IPFS)
  3. If vault unlocked: getDEK(userId) retrieves DEK from memory
  4. Title + content encrypted with encryptString(text, dek)
  5. Ciphertext + IV stored in IndexedDB
  6. Checksum (SHA-256) stored for integrity verification
  7. Linked to cloud via cloud_id field
Read Flow:
  1. useLocalStories() hook queries IndexedDB
  2. Returns encrypted records
  3. If vault unlocked: decryptString(ciphertext, iv, dek)
  4. Decrypted stories rendered in UI
  5. If vault locked: Shows “Unlock vault to view” placeholder
Non-blocking: Vault save failure never prevents cloud save success.

Security Best Practices

Use Strong PIN

  • Avoid simple patterns (123456, 111111)
  • Don’t reuse PINs from other services
  • 6 random digits recommended

Store PIN Securely

  • Write it down physically (not digitally)
  • Store in password manager
  • Never share with anyone

Lock Vault When Away

  • Click Lock Vault before stepping away
  • Auto-locks on sign-out
  • Prevents unauthorized access if device left open

Test Recovery

  • Save test story with vault
  • Lock vault, unlock with PIN
  • Verify story decrypts correctly

Preferences

Notification Settings (app/profile/ProfilePageClient.tsx:1034-1069)

Toggle on/off:
  • New follower alerts (future feature)
  • Tip received notifications
  • Weekly reflection summaries
  • System announcements
Currently local UI state. Will persist to database in future update.
Controls social feed visibility:
  • Public: Your public stories appear on social feed
  • Private: All stories hidden from social feed (even public ones)
Turning profile private hides all public stories, not just future ones.
Auto-suggest improvements:
  • Grammar/punctuation fixes
  • Sentence flow improvements
  • Clarity suggestions
Disabled by default. Enable to see AI suggestions after transcription.

Troubleshooting

Timezone Mismatch:
  • Streak uses server timestamp (UTC)
  • Your local time might be in different day
Solution:
  • Check created_at timestamps in database
  • Post earlier in your timezone
  • Streak recalculates on page load
Possible causes:
  • Typo in PIN entry
  • Caps Lock on (if using keyboard shortcuts)
  • Browser IndexedDB corrupted
Solution:
  1. Try entering PIN carefully
  2. Check browser console for crypto errors
  3. If truly forgotten, no recovery possible
  4. Clear IndexedDB and set up new vault (loses encrypted stories)
Avatar Source Priority:
  1. Manually uploaded avatar (future feature)
  2. Google OAuth avatar
  3. Default avatar based on first letter of name
Solution:
  • If using Google, update Google profile picture
  • Sign out and sign in again
  • Clear browser cache

Next Steps

Record More Stories

Build your streak and unlock achievements.

Explore Social Feed

Share your public stories with the community.

Build docs developers (and LLMs) love