objective categorical abstract machine language personal data server

Don't remove form from DOM while it's submitting

futur.blue 38c6d716 a871067c

verified
+99 -126
+99 -126
frontend/src/templates/MigratePage.mlx
··· 47 47 "error" 48 48 49 49 let step_to_number = function 50 - | EnterCredentials | ResumeAvailable | Enter2FA -> 51 - 0 52 - | ImportingData -> 50 + | EnterCredentials | ResumeAvailable | Enter2FA | ImportingData -> 53 51 1 54 52 | EnterPlcToken -> 55 53 2 ··· 104 102 </div> 105 103 end 106 104 107 - module LoadingOverlay = struct 108 - type loading_step = 109 - { label: string 110 - ; message: string 111 - ; duration: int (* ms before moving to next step *) } 112 - 113 - let steps = 114 - [| { label= "authenticating" 115 - ; message= "Logging into your current PDS..." 116 - ; duration= 2000 (* fake step *) } 117 - ; { label= "creating account" 118 - ; message= "Creating your account on this PDS..." 119 - ; duration= 2000 (* fake step *) } 120 - ; { label= "importing repository" 121 - ; message= "Importing your repository data..." 122 - ; duration= 0 (* stays here until page loads *) } |] 105 + module LoadingSpinner = struct 106 + let messages = 107 + [| "Logging into your current PDS..." 108 + ; "Creating your account on this PDS..." 109 + ; "Importing your repository data..." |] 123 110 124 111 let[@react.component] make () = 125 - let step_index, set_step_index = React.useState (fun () -> 0) in 126 - (* advance through steps on a timer *) 112 + let msg_index, set_msg_index = React.useState (fun () -> 0) in 113 + (* cycle through messages every 2 seconds *) 127 114 React.useEffect1 128 115 (fun () -> 129 - let current = steps.(step_index) in 130 - if current.duration > 0 && step_index < Array.length steps - 1 then 116 + if msg_index < Array.length messages - 1 then 131 117 let timer_id = 132 118 Js.Global.setTimeout 133 - ~f:(fun () -> set_step_index (fun i -> i + 1)) 134 - current.duration 119 + ~f:(fun () -> set_msg_index (fun i -> i + 1)) 120 + 2000 135 121 in 136 122 Some (fun () -> Js.Global.clearTimeout timer_id) 137 123 else None ) 138 - [|step_index|] ; 139 - let current = steps.(step_index) in 140 - let step_number = step_index + 1 in 141 - <div> 142 - <div className="mb-6"> 143 - <div className="flex items-center gap-1 text-sm text-mist-100 mb-2"> 144 - <span> 145 - (string 146 - ( "step " ^ string_of_int step_number ^ " of " 147 - ^ string_of_int total_steps ^ ": " ) ) 148 - </span> 149 - <span className="font-medium text-mana-100"> 150 - (string current.label) 151 - </span> 152 - </div> 153 - <div 154 - className="w-full bg-mist-60 rounded-full h-3" 155 - style=(ReactDOM.Style.make 156 - ~boxShadow:"0 4px 8px 0 rgba(115, 117, 121, 0.30) inset" () )> 157 - <div 158 - className="bg-mana-100 shadow-elixir h-3 rounded-full \ 159 - transition-all duration-500" 160 - style=(ReactDOM.Style.make 161 - ~width: 162 - (string_of_int (step_number * 100 / total_steps) ^ "%") 163 - () ) 164 - /> 165 - </div> 166 - </div> 167 - <div className="flex flex-col items-center py-8"> 168 - <div 169 - className="animate-spin w-8 h-8 border-2 border-mana-100 \ 170 - border-t-transparent rounded-full mb-4" 171 - /> 172 - <p className="text-mist-100 text-center">(string current.message)</p> 173 - </div> 124 + [|msg_index|] ; 125 + <div className="flex flex-col items-center py-8"> 126 + <div 127 + className="animate-spin w-8 h-8 border-2 border-mana-100 \ 128 + border-t-transparent rounded-full mb-4" 129 + /> 130 + <p className="text-mist-100 text-center">(string messages.(msg_index))</p> 174 131 </div> 175 132 end 176 133 ··· 186 143 let[@react.component] make ~csrf_token ~invite_required () = 187 144 let is_submitting, set_is_submitting = React.useState (fun () -> false) in 188 145 let handle_submit _ = set_is_submitting (fun _ -> true) in 189 - if is_submitting then <LoadingOverlay /> 190 - else 191 - array 192 - [| <p key="desc" className="w-full text-balance text-mist-100 mb-4"> 193 - (string 194 - "Migrate your existing atproto account to this PDS. You'll \ 195 - need your account credentials from your current PDS." ) 196 - </p> 197 - ; <form 198 - key="form" 199 - className="w-full flex flex-col gap-y-3" 200 - onSubmit=handle_submit> 201 - <input type_="hidden" name="dream.csrf" value=csrf_token /> 202 - <input type_="hidden" name="action" value="start_migration" /> 203 - ( if invite_required then 204 - <Input 205 - name="invite_code" 206 - type_="text" 207 - label="Invite code" 208 - placeholder="a1b2c3d4" 209 - required=true 210 - showIndicator=false /> 211 - else null ) 212 - <Input 213 - name="identifier" 214 - type_="text" 215 - label="Existing handle or DID" 216 - placeholder="you.bsky.social or did:plc:..." 217 - required=true 218 - showIndicator=false 219 - /> 220 - <Input 221 - name="password" 222 - type_="password" 223 - label="Existing password" 224 - required=true 225 - showIndicator=false 226 - /> 227 - <p className="text-xs text-mist-80 -mt-1"> 228 - (string 229 - "Use your actual account password, not an app password. Your \ 230 - password is only used to log into your current PDS and is \ 231 - never stored." ) 232 - </p> 233 - <Button type_="submit" formMethod="post" className="mt-2"> 234 - (string "start migration") 235 - </Button> 236 - </form> 237 - ; <div key="footer" className="mt-4"> 238 - <span className="text-sm text-mist-100"> 239 - (string "Don't have an account? ") 240 - <a 241 - href="/account/signup" 242 - className="text-mana-100 underline hover:text-mana-200"> 243 - (string "sign up") 244 - </a> 245 - (string " instead.") 246 - </span> 247 - </div> |] 146 + <div> 147 + ( if is_submitting then 148 + <div> 149 + <ProgressIndicator step_number=1 label="importing data" /> 150 + <LoadingSpinner /> 151 + </div> 152 + else null ) 153 + <div className=(if is_submitting then "hidden" else "")> 154 + <p className="w-full text-balance text-mist-100 mb-4"> 155 + (string 156 + "Migrate your existing atproto account to this PDS. You'll need \ 157 + your account credentials from your current PDS." ) 158 + </p> 159 + <form className="w-full flex flex-col gap-y-3" onSubmit=handle_submit> 160 + <input type_="hidden" name="dream.csrf" value=csrf_token /> 161 + <input type_="hidden" name="action" value="start_migration" /> 162 + ( if invite_required then 163 + <Input 164 + name="invite_code" 165 + type_="text" 166 + label="Invite code" 167 + placeholder="a1b2c3d4" 168 + required=true 169 + showIndicator=false /> 170 + else null ) 171 + <Input 172 + name="identifier" 173 + type_="text" 174 + label="Existing handle or DID" 175 + placeholder="you.bsky.social or did:plc:..." 176 + required=true 177 + showIndicator=false 178 + /> 179 + <Input 180 + name="password" 181 + type_="password" 182 + label="Existing password" 183 + required=true 184 + showIndicator=false 185 + /> 186 + <p className="text-xs text-mist-80 -mt-1"> 187 + (string 188 + "Use your actual account password, not an app password. Your \ 189 + password is only used to log into your current PDS and is \ 190 + never stored." ) 191 + </p> 192 + <Button type_="submit" formMethod="post" className="mt-2"> 193 + (string "start migration") 194 + </Button> 195 + </form> 196 + <div className="mt-4"> 197 + <span className="text-sm text-mist-100"> 198 + (string "Don't have an account? ") 199 + <a 200 + href="/account/signup" 201 + className="text-mana-100 underline hover:text-mana-200"> 202 + (string "sign up") 203 + </a> 204 + (string " instead.") 205 + </span> 206 + </div> 207 + </div> 208 + </div> 248 209 end 249 210 250 211 module ResumeForm = struct 251 212 let[@react.component] make ~csrf_token ~did () = 252 213 let is_submitting, set_is_submitting = React.useState (fun () -> false) in 253 214 let handle_submit _ = set_is_submitting (fun _ -> true) in 254 - if is_submitting then <LoadingOverlay /> 255 - else 256 - <div> 215 + <div> 216 + ( if is_submitting then 217 + <div> 218 + <ProgressIndicator step_number=1 label="importing data" /> 219 + <LoadingSpinner /> 220 + </div> 221 + else null ) 222 + <div className=(if is_submitting then "hidden" else "")> 257 223 <div 258 224 className="bg-mana-100/10 border border-mana-100/30 rounded-lg p-4 \ 259 225 mb-4"> ··· 309 275 </a> 310 276 </div> 311 277 </div> 278 + </div> 312 279 end 313 280 314 281 module TwoFactorForm = struct 315 282 let[@react.component] make ~csrf_token ~identifier ~old_pds ~invite_code () = 316 283 let is_submitting, set_is_submitting = React.useState (fun () -> false) in 317 284 let handle_submit _ = set_is_submitting (fun _ -> true) in 318 - if is_submitting then <LoadingOverlay /> 319 - else 320 - <div> 285 + <div> 286 + ( if is_submitting then 287 + <div> 288 + <ProgressIndicator step_number=1 label="importing data" /> 289 + <LoadingSpinner /> 290 + </div> 291 + else null ) 292 + <div className=(if is_submitting then "hidden" else "")> 321 293 <p className="text-mist-100 mb-4"> 322 294 (string 323 295 "Your account requires two-factor authentication. Check your \ ··· 363 335 </a> 364 336 </div> 365 337 </div> 338 + </div> 366 339 end 367 340 368 341 module BlobProgress = struct