Skip to content

Commit 782731c

Browse files
ChanMeng666claude
andcommitted
feat(ux): add comprehensive onboarding and help system for non-technical users
- Add welcome modal for first-time visitors with 3-step setup overview - Add setup progress tracker on homepage showing completion status - Add step-by-step API key setup guide in settings page - Add contextual help buttons (?) with detailed explanations for all fields - Add help content database covering API key, Audience ID, sender email, CSV import - Add SmartAlert component for user-friendly error messages with actionable guidance - Add error messages library with solutions for common issues - Track email sends for progress completion - Update contacts page with improved sync error messages and help buttons - Update send page with SmartAlert for missing configuration warnings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7e0c59e commit 782731c

13 files changed

Lines changed: 1613 additions & 63 deletions

File tree

app/contacts/page.tsx

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { useState, useEffect, useRef } from 'react'
4+
import Link from 'next/link'
45
import {
56
Plus,
67
Search,
@@ -13,11 +14,14 @@ import {
1314
X,
1415
Check,
1516
AlertCircle,
16-
Loader2
17+
Loader2,
18+
HelpCircle
1719
} from 'lucide-react'
1820
import { Button } from '@/components/ui/button'
1921
import { Input } from '@/components/ui/input'
2022
import { Card } from '@/components/ui/card'
23+
import { SmartAlert, SuccessAlert } from '@/components/shared/SmartAlert'
24+
import { HelpButton } from '@/components/help/HelpButton'
2125

2226
interface Contact {
2327
id: string
@@ -301,28 +305,34 @@ export default function ContactsPage() {
301305
Manage your email recipients
302306
</p>
303307
</div>
304-
<div className="flex gap-2">
305-
<Button
306-
variant="outline"
307-
className="neo-border"
308-
onClick={handleImportCSV}
309-
>
310-
<Upload className="w-4 h-4 mr-2" />
311-
Import CSV
312-
</Button>
313-
<Button
314-
variant="outline"
315-
className="neo-border"
316-
onClick={handleSyncResend}
317-
disabled={syncStatus === 'loading'}
318-
>
319-
{syncStatus === 'loading' ? (
320-
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
321-
) : (
322-
<RefreshCw className="w-4 h-4 mr-2" />
323-
)}
324-
Sync Resend
325-
</Button>
308+
<div className="flex gap-2 items-center">
309+
<div className="flex items-center gap-1">
310+
<Button
311+
variant="outline"
312+
className="neo-border"
313+
onClick={handleImportCSV}
314+
>
315+
<Upload className="w-4 h-4 mr-2" />
316+
Import CSV
317+
</Button>
318+
<HelpButton topic="csv-import" />
319+
</div>
320+
<div className="flex items-center gap-1">
321+
<Button
322+
variant="outline"
323+
className="neo-border"
324+
onClick={handleSyncResend}
325+
disabled={syncStatus === 'loading'}
326+
>
327+
{syncStatus === 'loading' ? (
328+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
329+
) : (
330+
<RefreshCw className="w-4 h-4 mr-2" />
331+
)}
332+
Sync Resend
333+
</Button>
334+
<HelpButton topic="resend-sync" />
335+
</div>
326336
<Button
327337
className="neo-button bg-neo-green text-white"
328338
onClick={() => setShowAddForm(true)}
@@ -334,15 +344,27 @@ export default function ContactsPage() {
334344
</div>
335345

336346
{/* Sync Status Message */}
337-
{syncMessage && (
338-
<div className={`mb-6 p-4 neo-border flex items-center gap-2 ${
339-
syncStatus === 'success' ? 'bg-green-50 text-green-800' :
340-
syncStatus === 'error' ? 'bg-red-50 text-red-800' :
341-
'bg-blue-50 text-blue-800'
342-
}`}>
343-
{syncStatus === 'success' && <Check className="w-5 h-5" />}
344-
{syncStatus === 'error' && <AlertCircle className="w-5 h-5" />}
345-
{syncStatus === 'loading' && <Loader2 className="w-5 h-5 animate-spin" />}
347+
{syncMessage && syncStatus === 'success' && (
348+
<SuccessAlert
349+
title="Sync Complete"
350+
description={syncMessage}
351+
className="mb-6"
352+
/>
353+
)}
354+
{syncMessage && syncStatus === 'error' && (
355+
<SmartAlert
356+
message={{
357+
title: 'Sync Failed',
358+
description: syncMessage,
359+
severity: 'error',
360+
action: syncMessage.includes('Settings') ? { text: 'Go to Settings', href: '/settings' } : undefined,
361+
}}
362+
className="mb-6"
363+
/>
364+
)}
365+
{syncMessage && syncStatus === 'loading' && (
366+
<div className="mb-6 p-4 neo-border flex items-center gap-2 bg-blue-50 text-blue-800 border-blue-300">
367+
<Loader2 className="w-5 h-5 animate-spin" />
346368
{syncMessage}
347369
</div>
348370
)}

app/page.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { useState, useEffect } from 'react'
34
import Link from 'next/link'
45
import {
56
Mail,
@@ -13,11 +14,41 @@ import {
1314
Newspaper
1415
} from 'lucide-react'
1516
import { Button } from '@/components/ui/button'
17+
import { WelcomeModal } from '@/components/onboarding/WelcomeModal'
18+
import { SetupProgress } from '@/components/progress/SetupProgress'
19+
20+
const ONBOARDING_KEY = 'email-platform-onboarding'
1621

1722
export default function HomePage() {
23+
const [showWelcome, setShowWelcome] = useState(false)
24+
25+
useEffect(() => {
26+
// Check if this is the first visit
27+
const saved = localStorage.getItem(ONBOARDING_KEY)
28+
if (!saved) {
29+
setShowWelcome(true)
30+
}
31+
}, [])
32+
33+
const handleWelcomeComplete = () => {
34+
localStorage.setItem(ONBOARDING_KEY, JSON.stringify({
35+
hasCompletedWelcome: true,
36+
completedAt: new Date().toISOString(),
37+
}))
38+
setShowWelcome(false)
39+
}
40+
1841
return (
1942
<div className="p-8">
20-
{/* 页面标题 */}
43+
{/* Welcome Modal for first-time visitors */}
44+
{showWelcome && (
45+
<WelcomeModal
46+
onGetStarted={handleWelcomeComplete}
47+
onSkip={handleWelcomeComplete}
48+
/>
49+
)}
50+
51+
{/* Page Header */}
2152
<div className="mb-8">
2253
<h1 className="text-4xl font-black uppercase tracking-tight mb-2">
2354
Email Template Platform
@@ -27,7 +58,10 @@ export default function HomePage() {
2758
</p>
2859
</div>
2960

30-
{/* 快速操作卡片 */}
61+
{/* Setup Progress - shows until all steps are complete */}
62+
<SetupProgress />
63+
64+
{/* Quick Action Cards */}
3165
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
3266
<QuickActionCard
3367
icon={<Palette className="w-8 h-8" />}

app/send/page.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import {
2121
import { Button } from '@/components/ui/button'
2222
import { Input } from '@/components/ui/input'
2323
import { Card } from '@/components/ui/card'
24+
import { SmartAlert } from '@/components/shared/SmartAlert'
25+
26+
const EMAILS_SENT_KEY = 'email-platform-emails-sent'
2427

2528
const TEMPLATES_STORAGE_KEY = 'email-platform-templates'
2629
const CONTACTS_STORAGE_KEY = 'email-platform-contacts'
@@ -475,18 +478,7 @@ function StepRecipients({
475478
return (
476479
<div>
477480
<h2 className="text-xl font-bold mb-4">Select Recipients</h2>
478-
<div className="flex items-center gap-2 p-4 bg-amber-50 border-2 border-amber-200 mb-4">
479-
<AlertCircle className="w-5 h-5 text-amber-600" />
480-
<span className="text-amber-700">
481-
No contacts found. Please add contacts first.
482-
</span>
483-
</div>
484-
<Link href="/contacts">
485-
<Button className="neo-button bg-neo-green text-white">
486-
Go to Contacts
487-
<ArrowRight className="w-4 h-4 ml-2" />
488-
</Button>
489-
</Link>
481+
<SmartAlert errorCode="NO_CONTACTS" className="mb-4" />
490482
</div>
491483
)
492484
}
@@ -871,26 +863,36 @@ function StepSend({
871863

872864
setSending(false)
873865
setSent(true)
866+
867+
// Track successful sends for progress tracking
868+
const successfulSends = sendResults.filter(r => r.success).length
869+
if (successfulSends > 0) {
870+
try {
871+
const current = parseInt(localStorage.getItem(EMAILS_SENT_KEY) || '0', 10)
872+
localStorage.setItem(EMAILS_SENT_KEY, String(current + successfulSends))
873+
} catch (e) {
874+
console.error('Failed to track emails sent:', e)
875+
}
876+
}
874877
}
875878

876879
if (!sent) {
877880
return (
878881
<div className="text-center">
879882
{error && (
880-
<div className="flex items-center gap-2 p-4 bg-red-50 border-2 border-red-200 mb-6 text-left">
881-
<AlertCircle className="w-5 h-5 text-red-600" />
882-
<span className="text-red-700">{error}</span>
883-
</div>
883+
<SmartAlert
884+
message={{
885+
title: 'Configuration Required',
886+
description: error,
887+
severity: 'error',
888+
action: { text: 'Go to Settings', href: '/settings' },
889+
}}
890+
className="mb-6 text-left"
891+
/>
884892
)}
885893

886-
{!settings.apiKey && (
887-
<div className="flex items-center gap-2 p-4 bg-amber-50 border-2 border-amber-200 mb-6 text-left">
888-
<AlertCircle className="w-5 h-5 text-amber-600" />
889-
<span className="text-amber-700">
890-
Please configure your Resend API Key in{' '}
891-
<Link href="/settings" className="underline font-bold">Settings</Link>
892-
</span>
893-
</div>
894+
{!settings.apiKey && !error && (
895+
<SmartAlert errorCode="API_KEY_MISSING" className="mb-6 text-left" />
894896
)}
895897

896898
<Send className="w-16 h-16 mx-auto mb-4 text-neo-green" />

app/settings/page.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { Input } from '@/components/ui/input'
77
import { Label } from '@/components/ui/label'
88
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
99
import { useToast } from '@/components/ui/use-toast'
10+
import { ApiKeySetupGuide } from '@/components/settings/ApiKeySetupGuide'
11+
import { HelpButton } from '@/components/help/HelpButton'
1012

1113
interface Settings {
1214
resendApiKey: string
@@ -100,7 +102,10 @@ export default function SettingsPage() {
100102
</CardHeader>
101103
<CardContent className="space-y-4">
102104
<div className="space-y-2">
103-
<Label htmlFor="apiKey">Resend API Key</Label>
105+
<div className="flex items-center gap-2">
106+
<Label htmlFor="apiKey">Resend API Key</Label>
107+
<HelpButton topic="resend-api-key" />
108+
</div>
104109
<div className="relative">
105110
<Input
106111
id="apiKey"
@@ -122,10 +127,15 @@ export default function SettingsPage() {
122127
)}
123128
</button>
124129
</div>
130+
{/* Show setup guide when API key is empty */}
131+
{!settings.resendApiKey && <ApiKeySetupGuide />}
125132
</div>
126133

127134
<div className="space-y-2">
128-
<Label htmlFor="audienceId">Audience ID (Optional)</Label>
135+
<div className="flex items-center gap-2">
136+
<Label htmlFor="audienceId">Audience ID (Optional)</Label>
137+
<HelpButton topic="audience-id" />
138+
</div>
129139
<Input
130140
id="audienceId"
131141
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
@@ -155,7 +165,10 @@ export default function SettingsPage() {
155165
</CardHeader>
156166
<CardContent className="space-y-4">
157167
<div className="space-y-2">
158-
<Label htmlFor="senderName">Sender Name</Label>
168+
<div className="flex items-center gap-2">
169+
<Label htmlFor="senderName">Sender Name</Label>
170+
<HelpButton topic="sender-name" />
171+
</div>
159172
<Input
160173
id="senderName"
161174
placeholder="Your Name"
@@ -166,7 +179,10 @@ export default function SettingsPage() {
166179
</div>
167180

168181
<div className="space-y-2">
169-
<Label htmlFor="senderEmail">Sender Email</Label>
182+
<div className="flex items-center gap-2">
183+
<Label htmlFor="senderEmail">Sender Email</Label>
184+
<HelpButton topic="sender-email" />
185+
</div>
170186
<Input
171187
id="senderEmail"
172188
type="email"
@@ -176,7 +192,8 @@ export default function SettingsPage() {
176192
className="neo-border"
177193
/>
178194
<p className="text-xs text-gray-500">
179-
Must be a verified domain in your Resend account
195+
Must be a verified domain in your Resend account.
196+
For testing, you can use <code className="bg-gray-100 px-1 rounded">delivered@resend.dev</code>
180197
</p>
181198
</div>
182199
</CardContent>

0 commit comments

Comments
 (0)