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}