ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
1import { useState } from "react";
2import { Heart, X, Check, ChevronRight } from "lucide-react";
3import { PLATFORMS } from "../config/platforms";
4import { ATPROTO_APPS } from "../config/atprotoApps";
5import type { UserSettings, PlatformDestinations } from "../types/settings";
6import ProgressBar from "./common/ProgressBar";
7import Card from "./common/Card";
8import PlatformBadge from "./common/PlatformBadge";
9import Toggle from "./common/Toggle";
10import DropdownWithIcons from "./common/DropdownWithIcons";
11import type { DropdownOptionWithIcon } from "./common/DropdownWithIcons";
12
13interface SetupWizardProps {
14 isOpen: boolean;
15 onClose: () => void;
16 onComplete: (settings: Partial<UserSettings>) => void;
17 currentSettings: UserSettings;
18}
19
20const wizardSteps = [
21 { title: "Welcome", description: "Set up your preferences" },
22 { title: "Platforms", description: "Choose where to import from" },
23 { title: "Destinations", description: "Where should matches go?" },
24 { title: "Privacy", description: "Data & automation settings" },
25 { title: "Ready!", description: "All set to find your people" },
26];
27
28export default function SetupWizard({
29 isOpen,
30 onClose,
31 onComplete,
32 currentSettings,
33}: SetupWizardProps) {
34 const [wizardStep, setWizardStep] = useState(0);
35 const [selectedPlatforms, setSelectedPlatforms] = useState<Set<string>>(
36 new Set(),
37 );
38 const [platformDestinations, setPlatformDestinations] =
39 useState<PlatformDestinations>(currentSettings.platformDestinations);
40 const [saveData, setSaveData] = useState(currentSettings.saveData);
41 const [enableAutomation, setEnableAutomation] = useState(
42 currentSettings.enableAutomation,
43 );
44 const [automationFrequency, setAutomationFrequency] = useState(
45 currentSettings.automationFrequency,
46 );
47
48 if (!isOpen) return null;
49
50 const handleComplete = () => {
51 onComplete({
52 platformDestinations,
53 saveData,
54 enableAutomation,
55 automationFrequency,
56 wizardCompleted: true,
57 });
58 onClose();
59 };
60
61 const togglePlatform = (platformKey: string) => {
62 const newSelected = new Set(selectedPlatforms);
63 if (newSelected.has(platformKey)) {
64 newSelected.delete(platformKey);
65 } else {
66 newSelected.add(platformKey);
67 }
68 setSelectedPlatforms(newSelected);
69 };
70
71 // Get platforms to show on destinations page (only selected ones)
72 const platformsToShow =
73 selectedPlatforms.size > 0
74 ? Object.entries(PLATFORMS).filter(([key]) => selectedPlatforms.has(key))
75 : Object.entries(PLATFORMS);
76
77 // Prepare app options with icons for dropdown
78 const appOptions: DropdownOptionWithIcon[] = Object.values(ATPROTO_APPS).map(
79 (app) => ({
80 value: app.id,
81 label: app.name,
82 icon: app.icon,
83 })
84 );
85
86 return (
87 <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
88 <Card
89 variant="wizard"
90 className="max-w-2xl w-full max-h-[90vh] flex flex-col"
91 >
92 {/* Header */}
93 <div className="px-6 py-4 border-b-2 border-cyan-500/30 dark:border-purple-500/30 flex-shrink-0">
94 <div className="flex items-center justify-between mb-3">
95 <div className="flex items-center space-x-3">
96 <div className="w-10 h-10 bg-firefly-banner dark:bg-firefly-banner-dark rounded-xl flex items-center justify-center shadow-md">
97 <Heart className="w-5 h-5 text-white" />
98 </div>
99 <h2 className="text-2xl font-bold text-purple-950 dark:text-cyan-50">
100 Setup Assistant
101 </h2>
102 </div>
103 <button
104 onClick={onClose}
105 className="text-purple-600 dark:text-cyan-400 hover:text-purple-950 dark:hover:text-cyan-50 transition-colors"
106 >
107 <X className="w-6 h-6" />
108 </button>
109 </div>
110 {/* Progress */}
111 <ProgressBar
112 current={wizardStep + 1}
113 total={wizardSteps.length}
114 variant="wizard"
115 />
116 <div className="mt-2 text-sm text-purple-750 dark:text-cyan-250">
117 Step {wizardStep + 1} of {wizardSteps.length}:{" "}
118 {wizardSteps[wizardStep].title}
119 </div>
120 </div>
121
122 {/* Content - Scrollable */}
123 <div className="px-6 py-4 overflow-y-auto flex-1">
124 {wizardStep === 0 && (
125 <div className="text-center space-y-3">
126 <div className="text-6xl mb-2">👋</div>
127 <h3 className="text-2xl font-bold text-purple-950 dark:text-cyan-50">
128 Welcome to ATlast!
129 </h3>
130 <p className="text-purple-750 dark:text-cyan-250 max-w-md mx-auto">
131 Let's get you set up in just a few steps. We'll help you
132 configure how you want to reconnect with your community on the
133 ATmosphere.
134 </p>
135 </div>
136 )}
137
138 {wizardStep === 1 && (
139 <div className="space-y-3">
140 <h3 className="text-xl font-bold text-purple-950 dark:text-cyan-50">
141 Which platforms will you import from?
142 </h3>
143 <p className="text-sm text-purple-750 dark:text-cyan-250">
144 Select one or more platforms you follow people on. We'll help
145 you find them on the ATmosphere.
146 </p>
147 <div className="grid grid-cols-3 gap-3 mt-3">
148 {Object.entries(PLATFORMS).map(([key, p]) => {
149 const isSelected = selectedPlatforms.has(key);
150 return (
151 <button
152 key={key}
153 onClick={() => togglePlatform(key)}
154 className={`p-4 rounded-xl border-2 transition-all relative ${
155 isSelected
156 ? "bg-purple-100/50 dark:bg-slate-950/50 border-2 border-purple-500 dark:border-cyan-500 text-purple-950 dark:text-cyan-50"
157 : "border-cyan-500/30 dark:border-purple-500/30 hover:bg-purple-100/50 dark:hover:bg-slate-950/50 hover:border-orange-500 dark:hover:border-amber-400"
158 }`}
159 >
160 {isSelected && (
161 <div className="absolute -top-2 -right-2 w-6 h-6 bg-orange-500 dark:bg-amber-400 rounded-full flex items-center justify-center shadow-md">
162 <Check className="w-4 h-4 text-white dark:text-slate-900" />
163 </div>
164 )}
165 <PlatformBadge
166 platformKey={key}
167 showName={false}
168 size="lg"
169 className="justify-center mb-2"
170 />
171 <div className="text-sm font-medium text-purple-950 dark:text-cyan-50">
172 {p.name}
173 </div>
174 </button>
175 );
176 })}
177 </div>
178 {selectedPlatforms.size > 0 && (
179 <div className="mt-3 px-3 py-2 rounded-lg border border-orange-650/30 dark:border-amber-400/30">
180 <p className="text-sm text-purple-750 dark:text-cyan-250">
181 ✨ {selectedPlatforms.size} platform
182 {selectedPlatforms.size !== 1 ? "s" : ""} selected
183 </p>
184 </div>
185 )}
186 </div>
187 )}
188
189 {wizardStep === 2 && (
190 <div className="space-y-4">
191 <h3 className="text-xl font-bold text-purple-950 dark:text-cyan-50">
192 Where should matches go?
193 </h3>
194 <p className="text-sm text-purple-750 dark:text-cyan-250">
195 Choose which ATmosphere app to use for each platform. You can
196 change this later.
197 </p>
198 <div className="space-y-4 mt-3">
199 {platformsToShow.map(([key, p]) => {
200 return (
201 <div
202 key={key}
203 className="flex items-center gap-3 px-3 max-w-lg mx-sm"
204 >
205 <PlatformBadge platformKey={key} size="sm" />
206 <DropdownWithIcons
207 value={
208 platformDestinations[
209 key as keyof PlatformDestinations
210 ]
211 }
212 onChange={(value) =>
213 setPlatformDestinations({
214 ...platformDestinations,
215 [key]: value,
216 })
217 }
218 options={appOptions}
219 className="ml-auto w-48"
220 />
221 </div>
222 );
223 })}
224 </div>
225 </div>
226 )}
227 {wizardStep === 3 && (
228 <div className="space-y-3">
229 <div>
230 <h3 className="text-xl font-bold text-purple-950 dark:text-cyan-50 mb-1">
231 Privacy & Automation
232 </h3>
233 <p className="text-sm text-purple-750 dark:text-cyan-250">
234 Control how your data is used.
235 </p>
236 </div>
237
238 <div className="space-y-4 px-4 py-3">
239 <Toggle
240 checked={saveData}
241 onChange={setSaveData}
242 label="Save my data for future checks"
243 description="Store your following lists so we can check for new matches later. You can delete anytime."
244 id="save-data"
245 />
246
247 <div>
248 <Toggle
249 checked={enableAutomation}
250 onChange={setEnableAutomation}
251 label="Notify me about new matches"
252 description="We'll check periodically and DM you when people you follow join the ATmosphere."
253 id="enable-automation"
254 />
255 {enableAutomation && (
256 <select
257 value={automationFrequency}
258 onChange={(e) =>
259 setAutomationFrequency(
260 e.target.value as "Weekly" | "Monthly" | "Quarterly"
261 )
262 }
263 className="mt-3 ml-auto max-w-xs px-3 py-2 bg-white dark:bg-slate-800 border border-cyan-500/30 dark:border-purple-500/30 rounded-lg text-sm w-full text-purple-950 dark:text-cyan-50 hover:border-cyan-400 dark:hover:border-purple-400 focus:outline-none focus:ring-2 focus:ring-orange-500 dark:focus:ring-amber-400 transition-colors"
264 >
265 <option value="daily">Check daily</option>
266 <option value="weekly">Check weekly</option>
267 <option value="monthly">Check monthly</option>
268 </select>
269 )}
270 </div>
271 </div>
272 </div>
273 )}
274
275 {wizardStep === 4 && (
276 <div className="text-center space-y-3">
277 <div className="text-6xl mb-2">🎉</div>
278 <h3 className="text-2xl font-bold text-purple-950 dark:text-cyan-50">
279 You're all set!
280 </h3>
281 <p className="text-purple-750 dark:text-cyan-250 max-w-md mx-auto">
282 Your preferences have been saved. You can change them anytime in
283 Settings.
284 </p>
285 <div className="px-4 py-3 mt-3">
286 <h4 className="font-semibold text-purple-950 dark:text-cyan-50 mb-2">
287 Quick Summary:
288 </h4>
289 <ul className="text-sm text-purple-750 dark:text-cyan-250 space-y-1 text-left max-w-sm mx-auto">
290 <li className="flex items-center space-x-2">
291 <Check className="w-4 h-4 text-orange-500" />
292 <span>
293 Data saving: {saveData ? "Enabled" : "Disabled"}
294 </span>
295 </li>
296 <li className="flex items-center space-x-2">
297 <Check className="w-4 h-4 text-orange-500" />
298 <span>
299 Automation: {enableAutomation ? "Enabled" : "Disabled"}
300 </span>
301 </li>
302 <li className="flex items-center space-x-2">
303 <Check className="w-4 h-4 text-orange-500" />
304 <span>
305 Platforms:{" "}
306 {selectedPlatforms.size > 0
307 ? selectedPlatforms.size
308 : "All"}{" "}
309 selected
310 </span>
311 </li>
312 <li className="flex items-center space-x-2">
313 <Check className="w-4 h-4 text-orange-500" />
314 <span>Ready to upload your first file!</span>
315 </li>
316 </ul>
317 </div>
318 </div>
319 )}
320 </div>
321
322 {/* Footer */}
323 <div className="px-6 py-4 border-t-2 border-cyan-500/30 dark:border-purple-500/30 flex items-center justify-between flex-shrink-0">
324 <button
325 onClick={() => wizardStep > 0 && setWizardStep(wizardStep - 1)}
326 disabled={wizardStep === 0}
327 className="px-4 py-2 text-purple-750 dark:text-cyan-250 hover:text-purple-950 dark:hover:text-cyan-50 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
328 >
329 Back
330 </button>
331 <button
332 onClick={() => {
333 if (wizardStep < wizardSteps.length - 1) {
334 setWizardStep(wizardStep + 1);
335 } else {
336 handleComplete();
337 }
338 }}
339 className="px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white rounded-lg font-medium shadow-md hover:shadow-lg transition-all flex items-center space-x-2"
340 >
341 <span>
342 {wizardStep === wizardSteps.length - 1 ? "Get Started" : "Next"}
343 </span>
344 {wizardStep < wizardSteps.length - 1 && (
345 <ChevronRight className="w-4 h-4" />
346 )}
347 </button>
348 </div>
349 </Card>
350 </div>
351 );
352}