+page.svelte (69539B)
1 <script lang="ts"> 2 import { goto } from "$app/navigation"; 3 import { 4 datastore, 5 db, 6 nostr_keys, 7 notif, 8 radroots, 9 route, 10 } from "$lib/utils/app"; 11 import { reset_sql_cipher } from "$lib/utils/app/cipher"; 12 import { 13 cfg_delay, 14 type AppData, 15 type ConfigData, 16 } from "$lib/utils/config"; 17 import { locale, ls } from "$lib/utils/i18n"; 18 import { get_default_nostr_relays } from "$lib/utils/nostr/lib"; 19 import { 20 carousel_create, 21 carousel_dec, 22 carousel_inc, 23 carousel_init, 24 el_id, 25 Fade, 26 fmt_id, 27 geop_is_valid, 28 Glyph, 29 sleep, 30 ViewPane, 31 ViewStack, 32 } from "@radroots/apps-lib"; 33 import { 34 ButtonLayoutPair, 35 CarouselContainer, 36 CarouselItem, 37 EntryLine, 38 EntryWrap, 39 FarmsAddMap, 40 LoadSymbol, 41 LogoCircle, 42 SelectMenu, 43 } from "@radroots/apps-lib-pwa"; 44 import { app_lo, app_loading } from "@radroots/apps-lib-pwa/stores/app"; 45 import type { AppConfigRole } from "@radroots/apps-lib-pwa/types/app"; 46 import { 47 geol_lat_fmt, 48 geol_lng_fmt, 49 type GeocoderReverseResult, 50 type GeolocationPoint, 51 } from "@radroots/geo"; 52 import { nostr_secret_key_validate } from "@radroots/nostr"; 53 import type { IError } from "@radroots/types-bindings"; 54 import { 55 err_msg, 56 form_fields, 57 handle_err, 58 type ResultPass, 59 } from "@radroots/utils"; 60 import { onMount } from "svelte"; 61 62 type View = "cfg_key" | "cfg_profile" | "cfg_bootstrap" | "eula"; 63 64 const page_carousel: Record<View, { max_index: number }> = { 65 cfg_key: { 66 max_index: 2, 67 }, 68 cfg_profile: { 69 max_index: 2, 70 }, 71 cfg_bootstrap: { 72 max_index: 2, 73 }, 74 eula: { 75 max_index: 1, 76 }, 77 }; 78 79 const carousel_cfg_key = carousel_create({ 80 view: "cfg_key", 81 max_index: page_carousel.cfg_key.max_index, 82 }); 83 84 const carousel_cfg_profile = carousel_create({ 85 view: "cfg_profile", 86 max_index: page_carousel.cfg_profile.max_index, 87 }); 88 89 const carousel_cfg_bootstrap = carousel_create({ 90 view: "cfg_bootstrap", 91 max_index: page_carousel.cfg_bootstrap.max_index, 92 }); 93 94 const carousel_eula = carousel_create({ 95 view: "eula", 96 max_index: page_carousel.eula.max_index, 97 }); 98 99 const view_carousel = { 100 cfg_key: carousel_cfg_key, 101 cfg_profile: carousel_cfg_profile, 102 cfg_bootstrap: carousel_cfg_bootstrap, 103 eula: carousel_eula, 104 }; 105 106 const carousel_cfg_profile_index = carousel_cfg_profile.index; 107 const carousel_cfg_bootstrap_index = carousel_cfg_bootstrap.index; 108 109 let view: View = $state("cfg_key"); 110 111 type CfgFarmOpt = "yes" | "no"; 112 type CfgBusinessOpt = "yes" | "no"; 113 114 let cfg_farm_opt: CfgFarmOpt | undefined = $state(undefined); 115 let cfg_business_opt: CfgBusinessOpt | undefined = $state(undefined); 116 type CfgKeyOpt = "nostr_key_gen" | "nostr_key_add"; 117 let cgf_key_opt: CfgKeyOpt | undefined = $state(undefined); 118 119 type CfgKeyStep = "intro" | "choice" | "add_existing"; 120 let cfg_key_step: CfgKeyStep = $state("intro"); 121 let cfg_key_loading = $state(false); 122 123 const cfg_key_step_index = (step: CfgKeyStep): number => { 124 switch (step) { 125 case "intro": 126 return 0; 127 case "choice": 128 return 1; 129 case "add_existing": 130 return 2; 131 } 132 }; 133 134 $effect(() => { 135 console.log(`view `, view); 136 console.log(`cfg_key_step `, cfg_key_step); 137 }); 138 139 const cfg_key_step_for_index = (index: number): CfgKeyStep => { 140 if (index <= 0) return "intro"; 141 if (index === 1) return "choice"; 142 return "add_existing"; 143 }; 144 145 let nostr_key_add_val = $state(``); 146 147 let profile_name_val = $state(``); 148 let profile_name_valid = $state(false); 149 let profile_name_nip05 = $state(true); 150 let profile_name_loading = $state(false); 151 152 let farm_name_val = $state(``); 153 let farm_products_val = $state(``); 154 let farm_map_geop: GeolocationPoint | undefined = $state(undefined); 155 let farm_map_geoc: GeocoderReverseResult | undefined = $state(undefined); 156 157 const farm_geop_lat = $derived.by(() => { 158 const farm_map_geop_value = farm_map_geop; 159 return geop_is_valid(farm_map_geop_value) 160 ? geol_lat_fmt(farm_map_geop_value.lat, `dms`, $locale, 3) 161 : ``; 162 }); 163 164 const farm_geop_lng = $derived.by(() => { 165 const farm_map_geop_value = farm_map_geop; 166 return geop_is_valid(farm_map_geop_value) 167 ? geol_lng_fmt(farm_map_geop_value.lng, `dms`, $locale, 3) 168 : ``; 169 }); 170 171 let is_eula_scrolled = $state(false); 172 let is_loading_s = $state(false); 173 174 const reset_local_state = (): void => { 175 cfg_farm_opt = undefined; 176 cfg_business_opt = undefined; 177 cgf_key_opt = undefined; 178 cfg_key_step = "intro"; 179 cfg_key_loading = false; 180 nostr_key_add_val = ``; 181 profile_name_val = ``; 182 profile_name_valid = false; 183 profile_name_nip05 = true; 184 profile_name_loading = false; 185 farm_name_val = ``; 186 farm_products_val = ``; 187 farm_map_geop = undefined; 188 farm_map_geoc = undefined; 189 is_eula_scrolled = false; 190 is_loading_s = false; 191 }; 192 193 const cfg_bootstrap_max_index = (): number => 194 cfg_farm_opt === "yes" ? page_carousel.cfg_bootstrap.max_index : 0; 195 196 const sync_carousel = (new_view: View, index: number): void => { 197 const carousel = view_carousel[new_view]; 198 carousel_init(carousel, { 199 index, 200 max_index: 201 new_view === "cfg_bootstrap" 202 ? cfg_bootstrap_max_index() 203 : page_carousel[new_view].max_index, 204 }); 205 }; 206 207 const set_cfg_key_step = (step: CfgKeyStep): void => { 208 cfg_key_step = step; 209 // @todo confirm why this was breaking the correct... if (step === "choice" && !cgf_key_opt) cgf_key_opt = "nostr_key_gen"; 210 sync_carousel("cfg_key", cfg_key_step_index(step)); 211 }; 212 213 onMount(async () => { 214 try { 215 await page_init(); 216 } catch (e) { 217 handle_err(e, `on_mount`); 218 } 219 }); 220 221 const page_init = async (): Promise<void> => { 222 reset_local_state(); 223 const nostr_keys_all = await nostr_keys.keys(); 224 if ("results" in nostr_keys_all) { 225 const confirm = await notif.confirm({ 226 message: `${$ls( 227 `notification.configuration.clear_prior_session`, 228 )}`, 229 }); 230 if (!confirm) { 231 await notif.alert( 232 `${$ls( 233 `notification.configuration.prior_session_pending`, 234 )}`, 235 ); 236 return; 237 } 238 await page_reset(); 239 return; 240 } 241 handle_view(`cfg_key`, { index: 0 }); 242 }; 243 244 const page_reset = async ( 245 alert_message?: string, 246 prevent_loading?: boolean, 247 ): Promise<void> => { 248 try { 249 console.log(`[config] page reset`); 250 app_loading.set(!prevent_loading); 251 reset_local_state(); 252 handle_view(`cfg_key`, { index: 0 }); 253 if (alert_message) await notif.alert(alert_message); 254 await sleep(cfg_delay.load); 255 await nostr_keys.reset(); 256 await datastore.reset(); 257 await reset_sql_cipher(db.get_store_key()); 258 await db.reinit(); 259 } catch (e) { 260 handle_err(e, `reset`); 261 } finally { 262 app_loading.set(false); 263 } 264 }; 265 266 const on_scroll_eula = async ({ 267 currentTarget: el, 268 }: { 269 currentTarget: EventTarget & HTMLDivElement; 270 }): Promise<void> => { 271 const client_h = el?.clientHeight; 272 const scroll_h = el?.scrollHeight; 273 const scroll_top = el?.scrollTop; 274 if (scroll_top + client_h >= scroll_h) is_eula_scrolled = true; 275 }; 276 277 const handle_view = (new_view: View, opts?: { index?: number }): void => { 278 let next_index = opts?.index ?? 0; 279 if (new_view === "cfg_key") { 280 if (opts?.index !== undefined) 281 cfg_key_step = cfg_key_step_for_index(opts.index); 282 else if (view === "cfg_profile") 283 cfg_key_step = 284 cgf_key_opt === "nostr_key_add" ? "add_existing" : "choice"; 285 if (cfg_key_step === "choice" && !cgf_key_opt) 286 cgf_key_opt = "nostr_key_gen"; 287 next_index = cfg_key_step_index(cfg_key_step); 288 } 289 view = new_view; 290 sync_carousel(new_view, next_index); 291 }; 292 293 $effect(() => { 294 console.log(`cgf_key_opt `, cgf_key_opt); 295 }); 296 297 const handle_config_err = async ( 298 err?: IError<string> | string, 299 ): Promise<void> => { 300 console.log(`err `, err); 301 let message = `${$ls(`error.init.configuration_failure`)}`; 302 if (typeof err === "string") message = err; 303 else if (err && "err" in err) message = err.err; 304 await page_reset(message); 305 }; 306 307 const handle_continue = async (): Promise<void> => { 308 switch (view) { 309 case `cfg_key`: 310 switch (cfg_key_step) { 311 case "intro": 312 return set_cfg_key_step("choice"); 313 case "choice": 314 return handle_new_key_or_add(); 315 case "add_existing": 316 return handle_key_add_existing(); 317 } 318 case `cfg_profile`: 319 switch ($carousel_cfg_profile_index) { 320 case 0: 321 return handle_setup_profile(); 322 case 1: 323 if (cfg_farm_opt === "no") 324 return carousel_inc(view_carousel[view]); 325 if (cfg_farm_opt === "yes") return handle_setup_role(); 326 return; 327 case 2: 328 if (cfg_farm_opt !== "no") return; 329 if (!cfg_business_opt) return; 330 return handle_setup_role(); 331 } 332 case `cfg_bootstrap`: 333 if (cfg_farm_opt === "yes") { 334 if ($carousel_cfg_bootstrap_index < 2) 335 return carousel_inc(view_carousel[view]); 336 return handle_view(`eula`); 337 } 338 return handle_view(`eula`); 339 } 340 }; 341 342 const handle_new_key_or_add = async (): Promise<void> => { 343 console.log(`RUNNING NOSTR KEY SETUP `, cgf_key_opt); 344 if (cgf_key_opt === `nostr_key_add`) 345 return set_cfg_key_step("add_existing"); 346 if (cfg_key_loading) return; 347 cfg_key_loading = true; 348 try { 349 console.log(`cfg_key_gen start`, { 350 view, 351 cfg_key_step, 352 cgf_key_opt, 353 }); 354 const key_created = await create_nostr_key(); 355 console.log(`cfg_key_gen result`, { key_created }); 356 if (!key_created) return; 357 handle_view(`cfg_profile`); 358 console.log(`cfg_key_gen view`, { view }); 359 } catch (e) { 360 console.log(`ERR `, e); 361 handle_err(e, `handle_new_key_or_add`); 362 } finally { 363 cfg_key_loading = false; 364 } 365 }; 366 367 const create_nostr_key = async (): Promise<boolean> => { 368 console.log(`cfg_key_gen keystore generate start`); 369 const keys_nostr_gen = await nostr_keys.generate(); 370 console.log(`keys_nostr_gen `, keys_nostr_gen); 371 if ("err" in keys_nostr_gen) { 372 console.log(`cfg_key_gen keystore generate err`, keys_nostr_gen); 373 await handle_config_err(keys_nostr_gen); 374 return false; 375 } 376 console.log(`cfg_key_gen keystore generate ok`); 377 const cfg_update = await datastore.update_obj<ConfigData>("cfg_data", { 378 nostr_public_key: keys_nostr_gen.public_key, 379 }); 380 console.log(`cfg_update `, cfg_update); 381 if ("err" in cfg_update) { 382 console.log(`cfg_key_gen datastore update err`, cfg_update); 383 await handle_config_err(cfg_update); 384 return false; 385 } 386 console.log(`cfg_key_gen datastore update ok`); 387 return true; 388 }; 389 390 const add_nostr_key = async (secret_key: string): Promise<boolean> => { 391 console.log(`cfg_key_add keystore add start`); 392 const keys_nostr_add = await nostr_keys.add(secret_key); 393 if ("err" in keys_nostr_add) { 394 console.log(`cfg_key_add keystore add err`, keys_nostr_add); 395 await notif.alert(`${$ls(`common.invalid_key`)}`); 396 return false; 397 } 398 console.log(`cfg_key_add keystore add ok`); 399 const cfg_update = await datastore.update_obj<ConfigData>("cfg_data", { 400 nostr_public_key: keys_nostr_add.public_key, 401 }); 402 if ("err" in cfg_update) { 403 console.log(`cfg_key_add datastore update err`, cfg_update); 404 await handle_config_err(cfg_update); 405 return false; 406 } 407 console.log(`cfg_key_add datastore update ok`); 408 return true; 409 }; 410 411 const handle_key_add_existing = async (): Promise<void> => { 412 if (cfg_key_loading) return; 413 let loading_set = false; 414 try { 415 if (!nostr_key_add_val) 416 return void (await notif.alert( 417 `${$ls(`icu.enter_a_*`, { 418 value: `${$ls(`common.nostr_key`)}`.toLowerCase(), 419 })}`, 420 )); 421 const secret_key = nostr_secret_key_validate(nostr_key_add_val); 422 if (!secret_key) 423 return void (await notif.alert( 424 `${$ls(`icu.not_a_valid_*`, { 425 value: `${$ls(`common.nostr_key`)}`.toLowerCase(), 426 })}`, 427 )); 428 cfg_key_loading = true; 429 loading_set = true; 430 console.log(`cfg_key_add start`, { 431 view, 432 cfg_key_step, 433 }); 434 const key_added = await add_nostr_key(secret_key); 435 console.log(`cfg_key_add result`, { key_added }); 436 if (!key_added) return; 437 nostr_key_add_val = ``; 438 handle_view(`cfg_profile`); 439 console.log(`cfg_key_add view`, { view }); 440 } catch (e) { 441 handle_err(e, `handle_key_add_existing`); 442 return void (await notif.alert( 443 `${$ls(`icu.not_a_valid_*`, { 444 value: `${$ls(`common.nostr_key`)}`.toLowerCase(), 445 })}`, 446 )); 447 } finally { 448 if (loading_set) cfg_key_loading = false; 449 } 450 }; 451 452 const handle_setup_profile = async (): Promise<void> => { 453 try { 454 if (profile_name_loading) return; 455 //@ 456 const ds_cfg_data = await datastore.get_obj<ConfigData>("cfg_data"); 457 console.log(`ds_cfg_data `, ds_cfg_data); 458 if ("err" in ds_cfg_data) return handle_config_err(); 459 460 const ks_nostr_key = await nostr_keys.read( 461 ds_cfg_data.result.nostr_public_key, 462 ); 463 console.log(`ks_nostr_key `, ks_nostr_key); 464 if ("err" in ks_nostr_key) return handle_config_err(); 465 466 if (profile_name_nip05) { 467 if (!profile_name_val) 468 return void (await notif.alert( 469 `${$ls(`icu.enter_a_*`, { 470 value: `${$ls( 471 `common.profile_name`, 472 )}`.toLowerCase(), 473 })}`, 474 )); 475 if (!profile_name_valid) 476 return void (await notif.alert( 477 `${$ls(`error.configuration.profile.name_min_length`)}`, 478 )); 479 profile_name_loading = true; 480 const accounts_req = await radroots.accounts_request({ 481 profile_name: profile_name_val, 482 secret_key: ks_nostr_key.secret_key, 483 }); 484 if ("err" in accounts_req) 485 return void (await notif.alert( 486 `${$ls(accounts_req.err, { 487 default: `${$ls( 488 `error.client.http.request_failure`, 489 )}`, 490 })}`, 491 )); 492 const confirm = await notif.confirm({ 493 message: `${`${$ls(`icu.the_*_is_available`, { 494 value: `${$ls( 495 `common.profile_name`, 496 ).toLowerCase()} "${profile_name_val}"`, 497 })}`}. ${`${$ls(`common.would_you_like_to_use_it_q`)}`}`, 498 cancel: `${$ls(`common.no`)}`, 499 ok: `${$ls(`common.yes`)}`, 500 }); 501 if (!confirm) return; 502 const accounts_create = await radroots.accounts_create({ 503 tok: accounts_req.result, 504 secret_key: ks_nostr_key.secret_key, 505 }); 506 if ("err" in accounts_create) 507 return void (await notif.alert( 508 `${$ls(accounts_create.err, { 509 default: `${$ls( 510 `error.client.http.request_failure`, 511 )}`, 512 })}`, 513 )); 514 await datastore.update_obj<ConfigData>("cfg_data", { 515 nip05_request: true, 516 nip05_key: accounts_create.result, 517 }); 518 } 519 520 if (!profile_name_val) { 521 const confirm = 522 await handle_add_profile_without_name_confirmation(); 523 if (!confirm) 524 return void el_id(fmt_id(`nostr:profile`))?.focus(); 525 } 526 527 if (profile_name_val) { 528 await datastore.update_obj<ConfigData>("cfg_data", { 529 nostr_profile: profile_name_val, 530 }); 531 } 532 533 await carousel_inc(view_carousel[view]); 534 } catch (e) { 535 handle_err(e, `handle_setup_profile`); 536 } finally { 537 profile_name_loading = false; 538 } 539 }; 540 541 const handle_add_profile_without_name_confirmation = 542 async (): Promise<boolean> => { 543 return await notif.confirm({ 544 message: `${$ls(`notification.init.no_profile_option`)}`, 545 cancel: `${$ls(`icu.add_*`, { 546 value: `${$ls(`common.profile`)}`, 547 })}`, 548 ok: `${$ls(`common.continue`)}`, 549 }); 550 }; 551 552 const cfg_role_resolve = (): AppConfigRole => { 553 if (cfg_farm_opt === "yes") return "farm"; 554 if (cfg_business_opt === "yes") return "business"; 555 return "individual"; 556 }; 557 558 const handle_setup_role = async (): Promise<void> => { 559 const role = cfg_role_resolve(); 560 await datastore.update_obj<ConfigData>("cfg_data", { role }); 561 handle_view(`cfg_bootstrap`); 562 }; 563 564 const handle_back = async (): Promise<void> => { 565 if (cfg_key_loading) return; 566 switch (view) { 567 case `cfg_key`: 568 switch (cfg_key_step) { 569 case "choice": { 570 cgf_key_opt = undefined; 571 return set_cfg_key_step("intro"); 572 } 573 case "add_existing": { 574 nostr_key_add_val = ``; 575 return set_cfg_key_step("choice"); 576 } 577 } 578 case `cfg_profile`: 579 switch ($carousel_cfg_profile_index) { 580 case 0: { 581 if (!profile_name_val) { 582 const confirm = 583 await handle_add_profile_without_name_confirmation(); 584 if (!confirm) 585 return void el_id( 586 fmt_id(`nostr:profile`), 587 )?.focus(); 588 return void carousel_inc(view_carousel[view]); 589 } 590 return handle_view(`cfg_key`); 591 } 592 case 1: 593 case 2: 594 return carousel_dec(view_carousel[view]); 595 } 596 case `cfg_bootstrap`: 597 if (cfg_farm_opt === "yes" && $carousel_cfg_bootstrap_index > 0) 598 return carousel_dec(view_carousel[view]); 599 return handle_view(`cfg_profile`, { 600 index: cfg_farm_opt === "no" ? 2 : 1, 601 }); 602 } 603 }; 604 605 const submit = async (): Promise<void> => { 606 try { 607 is_loading_s = true; 608 const ds_cfg_data = await datastore.get_obj<ConfigData>("cfg_data"); 609 if ("err" in ds_cfg_data) return handle_config_err(ds_cfg_data); 610 const active_key = ds_cfg_data.result?.nostr_public_key; 611 if (!active_key) 612 return handle_config_err(`${$ls(`error.init.no_active_key`)}`); 613 const ks_nostr_key = await nostr_keys.read(active_key); 614 if ("err" in ks_nostr_key) return handle_config_err(ks_nostr_key); 615 const configure_result = await configure_device( 616 active_key, 617 ds_cfg_data.result, 618 //ds_cfg_data.result.role || "personal", 619 //ds_cfg_data.result.nostr_profile, 620 ); 621 if ("err" in configure_result) { 622 return void (await notif.alert(configure_result.err)); 623 } else if (!("pass" in configure_result)) { 624 return void (await notif.alert( 625 `${$ls(`error.init.configuration_failure`)}`, 626 )); 627 } 628 const clear_cfg = await datastore.del_obj("cfg_data"); 629 if ("err" in clear_cfg) return handle_config_err(clear_cfg); 630 goto(`/`); 631 await notif.notify_init(); 632 } catch (e) { 633 handle_err(e, `submit`); 634 } finally { 635 is_loading_s = false; 636 } 637 }; 638 639 const configure_device = async ( 640 public_key: string, 641 config_data: ConfigData, 642 ): Promise<ResultPass | IError<string>> => { 643 const profile_type = 644 config_data.role === `farm` ? `farm` : `individual`; 645 const nostr_profile_add = await db.nostr_profile_create({ 646 public_key, 647 profile_type, 648 name: config_data.nostr_profile 649 ? config_data.nostr_profile 650 : `${$ls(`common.default`)}`, 651 display_name: config_data.nostr_profile 652 ? config_data.nostr_profile 653 : undefined, 654 }); 655 if ("err" in nostr_profile_add) 656 return err_msg( 657 `${$ls(`error.init.device_configuration_nostr_profile`)}`, 658 ); 659 for (const url of get_default_nostr_relays()) { 660 const nostr_relay_add = await db.nostr_relay_create({ url }); 661 if ("err" in nostr_relay_add) 662 return err_msg( 663 `${$ls(`error.init.device_configuration_nostr_relay`)}`, 664 ); 665 const nostr_profile_relay_set = await db.nostr_profile_relay_set({ 666 nostr_profile: { 667 id: nostr_profile_add.result.id, 668 }, 669 nostr_relay: { 670 id: nostr_relay_add.result.id, 671 }, 672 }); 673 if ("err" in nostr_profile_relay_set) 674 return err_msg( 675 `${$ls( 676 `error.init.device_configuration_nostr_profile_relay`, 677 )}`, 678 ); 679 } 680 const set_app_data = await datastore.set_obj<AppData>("app_data", { 681 active_key: public_key, 682 role: config_data.role || "individual", 683 eula_date: new Date().toISOString(), 684 nip05_key: config_data.nip05_key, 685 }); 686 if ("err" in set_app_data) return err_msg(set_app_data); 687 await datastore.del_obj("cfg_data"); 688 return { pass: true }; 689 }; 690 691 $effect(() => { 692 console.log(`view `, view); 693 }); 694 </script> 695 696 {#if view === "cfg_key" && cfg_key_step !== "intro"} 697 <Fade basis={{ classes: `z-10 absolute top-8 right-6` }}> 698 <SelectMenu 699 basis={{ 700 layer: 0, 701 options: [ 702 { 703 entries: [ 704 { 705 value: "", 706 label: `${$ls(`common.choose_options`)}`, 707 disabled: true, 708 }, 709 { 710 value: "import", 711 label: `${$ls(`common.import_backup`)}`, 712 }, 713 ], 714 }, 715 ], 716 callback: async ({ value }) => { 717 if (value === "import") 718 return void (await route("/import")); 719 }, 720 }} 721 > 722 <Glyph 723 basis={{ 724 classes: `text-base text-ly0-gl group-active:text-ly0-gl-a`, 725 key: "gear", 726 }} 727 /> 728 </SelectMenu> 729 </Fade> 730 {/if} 731 732 <ViewStack 733 basis={{ 734 active_view: view, 735 }} 736 > 737 <ViewPane basis={{ view: "cfg_key" }}> 738 <div class={`flex flex-col h-full w-full justify-start items-center`}> 739 <CarouselContainer 740 basis={{ 741 carousel: carousel_cfg_key, 742 }} 743 > 744 <CarouselItem 745 basis={{ 746 classes: `justify-center items-center`, 747 }} 748 > 749 <div 750 class={`relative flex flex-col h-full w-full justify-center items-center`} 751 > 752 <div 753 class={`flex flex-row w-full justify-start items-center -translate-y-16`} 754 > 755 <button 756 class={`flex flex-row w-full justify-center items-center`} 757 onclick={async () => { 758 await goto(`/`); 759 }} 760 > 761 <LogoCircle /> 762 </button> 763 </div> 764 <div 765 class={`absolute bottom-0 left-0 flex flex-col h-[20rem] w-full px-10 gap-2 justify-start items-center`} 766 > 767 <div 768 class={`flex flex-row w-full justify-start items-center`} 769 > 770 <p 771 class={`font-sans font-[400] text-sm text-ly0-gl-label uppercase`} 772 > 773 {`${$ls(`common.configure`)}`} 774 </p> 775 </div> 776 <div 777 class={`flex flex-col w-full gap-2 justify-start items-center`} 778 > 779 <div 780 class={`flex flex-row w-full justify-start items-center`} 781 > 782 <p 783 class={`font-mono font-[400] text-[1.1rem] text-ly0-gl`} 784 > 785 {`${$ls(`notification.init.greeting_header`)}`} 786 </p> 787 </div> 788 <div 789 class={`flex flex-row w-full justify-start items-center`} 790 > 791 <p 792 class={`font-mono font-[400] text-[1.1rem] text-ly0-gl`} 793 > 794 {`${$ls( 795 `notification.init.greeting_subheader`, 796 )}.`} 797 </p> 798 </div> 799 </div> 800 </div> 801 </div> 802 </CarouselItem> 803 <CarouselItem 804 basis={{ 805 classes: `justify-center items-center`, 806 }} 807 > 808 <div 809 class={`flex flex-col h-[16rem] gap-8 w-full justify-start items-center`} 810 > 811 <div 812 class={`flex flex-row w-full justify-center items-center`} 813 > 814 <p 815 class={`font-sans font-[600] text-ly0-gl text-3xl`} 816 > 817 {`${$ls(`icu.configure_*`, { 818 value: `${$ls(`common.device`)}`, 819 })}`} 820 </p> 821 </div> 822 <div 823 class={`flex flex-col w-full gap-6 justify-center items-center`} 824 > 825 <button 826 class={`flex flex-col h-bold_button w-lo_${$app_lo} justify-center items-center rounded-touch ${ 827 cgf_key_opt === `nostr_key_gen` 828 ? `ly1-apply-active ly1-raise-apply ly1-ring-apply` 829 : `bg-ly1` 830 } el-re`} 831 onclick={async (ev) => { 832 ev.stopPropagation(); 833 cgf_key_opt = `nostr_key_gen`; 834 }} 835 > 836 <p 837 class={`font-sans font-[600] text-ly0-gl text-xl`} 838 > 839 {`${$ls(`icu.create_new_*`, { 840 value: `${$ls( 841 `common.keypair`, 842 )}`.toLowerCase(), 843 })}`} 844 </p> 845 </button> 846 <button 847 class={`flex flex-col h-bold_button w-lo_${$app_lo} justify-center items-center rounded-touch ${ 848 cgf_key_opt === `nostr_key_add` 849 ? `ly1-apply-active ly1-raise-apply ly1-ring-apply` 850 : `bg-ly1` 851 } el-re`} 852 onclick={async (ev) => { 853 ev.stopPropagation(); 854 cgf_key_opt = `nostr_key_add`; 855 }} 856 > 857 <p 858 class={`font-sans font-[600] text-ly0-gl text-xl`} 859 > 860 {`${$ls(`icu.use_existing_*`, { 861 value: `${$ls( 862 `common.keypair`, 863 )}`.toLowerCase(), 864 })}`} 865 </p> 866 </button> 867 </div> 868 </div> 869 </CarouselItem> 870 <CarouselItem 871 basis={{ 872 classes: `justify-center items-center`, 873 }} 874 > 875 <div 876 class={`flex flex-col w-full gap-8 justify-start items-center`} 877 > 878 <div 879 class={`flex flex-col w-full gap-6 justify-center items-center`} 880 > 881 <p 882 class={`font-sans font-[600] text-ly0-gl text-3xl capitalize`} 883 > 884 {`${$ls(`icu.add_existing_*`, { 885 value: `${$ls(`common.key`)}`.toLowerCase(), 886 })}`} 887 </p> 888 <EntryLine 889 bind:value={nostr_key_add_val} 890 basis={{ 891 wrap: { 892 layer: 1, 893 classes: `w-lo_${$app_lo}`, 894 style: `guide`, 895 }, 896 el: { 897 classes: `font-sans text-[1.25rem] text-center placeholder:opacity-60`, 898 layer: 1, 899 placeholder: `${$ls(`icu.enter_*`, { 900 value: `${$ls( 901 `common.nostr_nsec_hex`, 902 )}`, 903 })}`, 904 callback_keydown: async ({ 905 key_s, 906 el, 907 }) => { 908 if (key_s) { 909 el.blur(); 910 handle_continue(); 911 } 912 }, 913 }, 914 }} 915 /> 916 </div> 917 </div> 918 </CarouselItem> 919 <div 920 class={`z-10 absolute ios0:bottom-2 bottom-10 left-0 flex flex-col w-full justify-center items-center`} 921 > 922 <ButtonLayoutPair 923 basis={{ 924 continue: { 925 label: `${$ls(`common.continue`)}`, 926 disabled: 927 cfg_key_loading || 928 (cfg_key_step === "choice" && !cgf_key_opt), 929 loading: cfg_key_loading, 930 callback: async () => handle_continue(), 931 }, 932 back: { 933 label: `${$ls(`common.back`)}`, 934 visible: cfg_key_step !== "intro", 935 disabled: cfg_key_loading, 936 callback: async () => handle_back(), 937 }, 938 }} 939 /> 940 </div> 941 </CarouselContainer> 942 </div> 943 </ViewPane> 944 945 <ViewPane basis={{ view: "cfg_profile" }}> 946 <div class={`flex flex-col h-full w-full justify-start items-center`}> 947 <CarouselContainer 948 basis={{ 949 carousel: carousel_cfg_profile, 950 }} 951 > 952 <CarouselItem 953 basis={{ 954 classes: `justify-center items-center`, 955 }} 956 > 957 <div 958 class={`flex flex-col h-[16rem] w-full px-4 gap-6 justify-start items-center`} 959 > 960 <p class={`font-sans font-[600] text-ly0-gl text-3xl`}> 961 {`${$ls(`icu.add_*`, { 962 value: `${$ls(`common.profile`)}`, 963 })}`} 964 </p> 965 <div 966 class={`flex flex-col w-full gap-4 justify-center items-center`} 967 > 968 <EntryLine 969 bind:value={profile_name_val} 970 basis={{ 971 loading: profile_name_loading, 972 wrap: { 973 layer: 1, 974 classes: `w-lo_${$app_lo}`, 975 style: `guide`, 976 }, 977 el: { 978 classes: `font-sans text-[1.25rem] text-center placeholder:opacity-60`, 979 id: fmt_id(`nostr:profile`), 980 layer: 1, 981 placeholder: `${$ls(`icu.enter_*`, { 982 value: `${$ls( 983 `common.profile_name`, 984 )}`.toLowerCase(), 985 })}`, 986 field: form_fields.profile_name, 987 callback: async ({ pass }) => { 988 profile_name_valid = pass; 989 }, 990 callback_keydown: async ({ 991 key_s, 992 el, 993 }) => { 994 if (key_s) { 995 el.blur(); 996 handle_continue(); 997 } 998 }, 999 }, 1000 }} 1001 /> 1002 <div 1003 class={`flex flex-row w-full gap-2 justify-center items-center`} 1004 > 1005 <input 1006 type="checkbox" 1007 bind:checked={profile_name_nip05} 1008 /> 1009 <button 1010 class={`flex flex-row justify-center items-center`} 1011 onclick={async () => { 1012 profile_name_nip05 = 1013 !profile_name_nip05; 1014 }} 1015 > 1016 <p 1017 class={`font-sans font-[400] text-ly0-gl text-[14px] tracking-wide`} 1018 > 1019 {`${$ls(`common.create`)}`} 1020 <span 1021 class={`font-mono font-[500] tracking-tight px-[3px]`} 1022 > 1023 {`@radroots`} 1024 </span> 1025 {`${$ls(`common.nip05_address`)}`} 1026 </p> 1027 </button> 1028 </div> 1029 </div> 1030 </div> 1031 </CarouselItem> 1032 <CarouselItem 1033 basis={{ 1034 classes: `justify-center items-center`, 1035 role: `button`, 1036 tabindex: 0, 1037 callback_click: async () => { 1038 cfg_farm_opt = undefined; 1039 cfg_business_opt = undefined; 1040 }, 1041 }} 1042 > 1043 <div 1044 class={`flex flex-col h-[16rem] w-full gap-10 justify-start items-center`} 1045 > 1046 <div 1047 class={`flex flex-row w-full justify-center items-center`} 1048 > 1049 <p 1050 class={`font-sans font-[600] text-ly0-gl text-3xl`} 1051 > 1052 {`${$ls(`common.setup_for_farmer`)}`} 1053 </p> 1054 </div> 1055 <div 1056 class={`flex flex-col w-full gap-5 justify-center items-center`} 1057 > 1058 <button 1059 class={`flex flex-col h-bold_button w-lo_${$app_lo} justify-center items-center rounded-touch ${ 1060 cfg_farm_opt === `yes` 1061 ? `ly1-apply-active ly1-raise-apply ly1-ring-apply` 1062 : `bg-ly1` 1063 } el-re`} 1064 onclick={async (ev) => { 1065 ev.stopPropagation(); 1066 cfg_farm_opt = `yes`; 1067 }} 1068 > 1069 <p 1070 class={`font-sans font-[600] text-ly0-gl text-xl`} 1071 > 1072 {`${$ls(`common.yes`)}`} 1073 </p> 1074 </button> 1075 <button 1076 class={`flex flex-col h-bold_button w-lo_${$app_lo} justify-center items-center rounded-touch ${ 1077 cfg_farm_opt === `no` 1078 ? `ly1-apply-active ly1-raise-apply ly1-ring-apply` 1079 : `bg-ly1` 1080 } el-re`} 1081 onclick={async (ev) => { 1082 ev.stopPropagation(); 1083 cfg_farm_opt = `no`; 1084 }} 1085 > 1086 <p 1087 class={`font-sans font-[600] text-ly0-gl text-xl`} 1088 > 1089 {`${$ls(`common.no`)}`} 1090 </p> 1091 </button> 1092 </div> 1093 </div> 1094 </CarouselItem> 1095 <CarouselItem 1096 basis={{ 1097 classes: `justify-center items-center`, 1098 role: `button`, 1099 tabindex: 0, 1100 callback_click: async () => { 1101 cfg_business_opt = undefined; 1102 }, 1103 }} 1104 > 1105 <div 1106 class={`flex flex-col h-[16rem] w-full gap-10 justify-start items-center`} 1107 > 1108 <div 1109 class={`flex flex-row w-full justify-center items-center`} 1110 > 1111 <p 1112 class={`font-sans font-[600] text-ly0-gl text-3xl`} 1113 > 1114 {`${$ls(`common.setup_for_business`)}`} 1115 </p> 1116 </div> 1117 <div 1118 class={`flex flex-col w-full gap-5 justify-center items-center`} 1119 > 1120 <button 1121 class={`flex flex-col h-bold_button w-lo_${$app_lo} justify-center items-center rounded-touch ${ 1122 cfg_business_opt === `yes` 1123 ? `ly1-apply-active ly1-raise-apply ly1-ring-apply` 1124 : `bg-ly1` 1125 } el-re`} 1126 onclick={async (ev) => { 1127 ev.stopPropagation(); 1128 cfg_business_opt = `yes`; 1129 }} 1130 > 1131 <p 1132 class={`font-sans font-[600] text-ly0-gl text-xl`} 1133 > 1134 {`${$ls(`common.yes`)}`} 1135 </p> 1136 </button> 1137 <button 1138 class={`flex flex-col h-bold_button w-lo_${$app_lo} justify-center items-center rounded-touch ${ 1139 cfg_business_opt === `no` 1140 ? `ly1-apply-active ly1-raise-apply ly1-ring-apply` 1141 : `bg-ly1` 1142 } el-re`} 1143 onclick={async (ev) => { 1144 ev.stopPropagation(); 1145 cfg_business_opt = `no`; 1146 }} 1147 > 1148 <p 1149 class={`font-sans font-[600] text-ly0-gl text-xl`} 1150 > 1151 {`${$ls(`common.no`)}`} 1152 </p> 1153 </button> 1154 </div> 1155 </div> 1156 </CarouselItem> 1157 </CarouselContainer> 1158 <div 1159 class={`absolute ios0:bottom-2 bottom-10 left-0 flex flex-col w-full justify-center items-center`} 1160 > 1161 <ButtonLayoutPair 1162 basis={{ 1163 continue: { 1164 label: `${$ls(`common.continue`)}`, 1165 disabled: 1166 ($carousel_cfg_profile_index === 1 && 1167 !cfg_farm_opt) || 1168 ($carousel_cfg_profile_index === 2 && 1169 (cfg_farm_opt !== "no" || 1170 !cfg_business_opt)), 1171 callback: async () => handle_continue(), 1172 }, 1173 back: { 1174 visible: true, 1175 label: 1176 view === "cfg_profile" && 1177 $carousel_cfg_profile_index === 0 && 1178 !profile_name_val 1179 ? `${$ls(`common.skip`)}` 1180 : `${$ls(`common.back`)}`, 1181 callback: handle_back, 1182 }, 1183 }} 1184 /> 1185 </div> 1186 </div> 1187 </ViewPane> 1188 1189 <ViewPane basis={{ view: "cfg_bootstrap" }}> 1190 <div class={`flex flex-col h-full w-full justify-start items-center`}> 1191 <CarouselContainer 1192 basis={{ 1193 carousel: carousel_cfg_bootstrap, 1194 }} 1195 > 1196 {#if cfg_farm_opt === "yes"} 1197 <CarouselItem 1198 basis={{ 1199 classes: `justify-center items-center`, 1200 }} 1201 > 1202 <div 1203 class={`flex flex-col h-[16rem] w-full px-4 gap-6 justify-start items-center`} 1204 > 1205 <p 1206 class={`font-sans font-[600] text-ly0-gl text-3xl`} 1207 > 1208 {`${$ls(`common.farm_name`)}`} 1209 </p> 1210 <EntryWrap 1211 basis={{ 1212 layer: 1, 1213 classes: `w-lo_${$app_lo}`, 1214 style: `guide`, 1215 }} 1216 > 1217 <input 1218 bind:value={farm_name_val} 1219 class={`h-entry_line w-full font-sans text-[1.25rem] text-center placeholder:opacity-60 el-input bg-ly1 text-ly1-gl placeholder:text-ly1-gl_pl caret-ly1-gl el-re`} 1220 placeholder={`${$ls(`common.farm_name`)}`} 1221 /> 1222 </EntryWrap> 1223 </div> 1224 </CarouselItem> 1225 <CarouselItem 1226 basis={{ 1227 classes: `justify-start items-center`, 1228 }} 1229 > 1230 <FarmsAddMap 1231 bind:map_geop={farm_map_geop} 1232 bind:map_geoc={farm_map_geoc} 1233 {farm_geop_lat} 1234 {farm_geop_lng} 1235 /> 1236 </CarouselItem> 1237 <CarouselItem 1238 basis={{ 1239 classes: `justify-center items-center`, 1240 }} 1241 > 1242 <div 1243 class={`flex flex-col h-[16rem] w-full px-4 gap-6 justify-start items-center`} 1244 > 1245 <p 1246 class={`font-sans font-[600] text-ly0-gl text-3xl`} 1247 > 1248 {`${$ls(`common.products`)}`} 1249 </p> 1250 <EntryWrap 1251 basis={{ 1252 layer: 1, 1253 classes: `w-lo_${$app_lo}`, 1254 style: `guide`, 1255 no_pad: true, 1256 }} 1257 > 1258 <textarea 1259 bind:value={farm_products_val} 1260 class={`h-full w-full min-h-[8rem] px-6 py-4 font-sans text-[1.05rem] text-center placeholder:opacity-60 el-input bg-ly1 text-ly1-gl placeholder:text-ly1-gl_pl caret-ly1-gl el-re resize-none`} 1261 placeholder={`${$ls(`common.products`)}`} 1262 ></textarea> 1263 </EntryWrap> 1264 </div> 1265 </CarouselItem> 1266 {:else} 1267 <CarouselItem 1268 basis={{ 1269 classes: `justify-center items-center`, 1270 }} 1271 > 1272 <div 1273 class={`flex flex-col h-[16rem] w-full px-4 gap-6 justify-center items-center`} 1274 > 1275 <p 1276 class={`font-sans font-[600] text-ly0-gl text-3xl capitalize`} 1277 > 1278 {cfg_role_resolve()} 1279 </p> 1280 </div> 1281 </CarouselItem> 1282 {/if} 1283 </CarouselContainer> 1284 <div 1285 class={`absolute ios0:bottom-2 bottom-10 left-0 flex flex-col w-full justify-center items-center`} 1286 > 1287 <ButtonLayoutPair 1288 basis={{ 1289 continue: { 1290 label: `${$ls(`common.continue`)}`, 1291 callback: async () => handle_continue(), 1292 }, 1293 back: { 1294 visible: true, 1295 label: `${$ls(`common.back`)}`, 1296 callback: handle_back, 1297 }, 1298 }} 1299 /> 1300 </div> 1301 </div> 1302 </ViewPane> 1303 1304 <ViewPane basis={{ view: "eula" }}> 1305 <div 1306 class={`flex flex-col h-full w-full ios0:pt-12 pt-24 justify-start items-center`} 1307 > 1308 <CarouselContainer 1309 basis={{ 1310 carousel: carousel_eula, 1311 classes: `rounded-2xl scroll-hide`, 1312 }} 1313 > 1314 <CarouselItem 1315 basis={{ 1316 classes: `justify-start items-center`, 1317 }} 1318 > 1319 <div 1320 class={`flex flex-col h-full w-full px-4 justify-start items-center ${ 1321 view === `eula` ? `fade-in-long` : `` 1322 }`} 1323 > 1324 <div 1325 class={`flex flex-col w-full px-4 gap-4 justify-start items-center`} 1326 > 1327 <div 1328 class={`flex flex-row w-full ios0:pt-8 justify-center items-center`} 1329 > 1330 <p 1331 class={`font-mono font-[600] text-ly0-gl text-2xl`} 1332 > 1333 {`${$ls(`eula.title`)}`} 1334 </p> 1335 </div> 1336 <div 1337 class={`flex flex-col ios0:h-[26rem] ios1:h-[38rem] w-full gap-6 justify-start items-center overflow-y-scroll scroll-hide`} 1338 onscroll={on_scroll_eula} 1339 > 1340 <div 1341 class={`flex flex-col w-full gap-2 justify-start items-start`} 1342 > 1343 <p 1344 class={`font-mono font-[600] text-ly0-gl`} 1345 > 1346 {`**${$ls(`eula.introduction.title`)}**`} 1347 </p> 1348 <p 1349 class={`font-mono font-[500] text-ly0-gl text-sm text-justify break-word`} 1350 > 1351 {`${$ls(`eula.introduction.body`)}`} 1352 </p> 1353 </div> 1354 <div 1355 class={`flex flex-col w-full gap-2 justify-start items-start`} 1356 > 1357 <p 1358 class={`font-mono font-[600] text-ly0-gl`} 1359 > 1360 {`**${$ls(`eula.prohibited_content.title`)}**`} 1361 </p> 1362 <p 1363 class={`font-mono font-[500] text-sm text-ly0-gl text-justify break-word`} 1364 > 1365 {`${$ls( 1366 `eula.prohibited_content.body_0_title`, 1367 )}`} 1368 </p> 1369 <div 1370 class={`flex flex-col w-full justify-start items-start`} 1371 > 1372 {#each [0, 1, 2, 3, 4, 5] as li} 1373 <div 1374 class={`flex flex-row w-full justify-start items-center`} 1375 > 1376 <div 1377 class={`flex flex-row h-full w-8 justify-start items-start`} 1378 > 1379 <p 1380 class={` font-mono font-[500] text-sm text-ly0-gl text-justify break-word`} 1381 > 1382 {`*`} 1383 </p> 1384 </div> 1385 <div 1386 class={`flex flex-row h-full w-full justify-start items-start`} 1387 > 1388 <p 1389 class={`col-span-10 font-mono font-[500] text-sm text-ly0-gl text-justify break-word`} 1390 > 1391 {`${$ls( 1392 `eula.prohibited_content.body_li_0_${li}`, 1393 )}`} 1394 </p> 1395 </div> 1396 </div> 1397 {/each} 1398 </div> 1399 </div> 1400 <div 1401 class={`flex flex-col w-full gap-2 justify-start items-start`} 1402 > 1403 <p 1404 class={`font-mono font-[600] text-ly0-gl`} 1405 > 1406 {`**${$ls(`eula.prohibited_conduct.title`)}**`} 1407 </p> 1408 <div 1409 class={`flex flex-col w-full justify-start items-start`} 1410 > 1411 {#each [0, 1, 2, 3] as li} 1412 <div 1413 class={`flex flex-row w-full justify-start items-center`} 1414 > 1415 <div 1416 class={`flex flex-row h-full w-8 justify-start items-start`} 1417 > 1418 <p 1419 class={` font-mono font-[500] text-sm text-ly0-gl text-justify break-word`} 1420 > 1421 {`*`} 1422 </p> 1423 </div> 1424 <div 1425 class={`flex flex-row h-full w-full justify-start items-start`} 1426 > 1427 <p 1428 class={`col-span-10 font-mono font-[500] text-sm text-ly0-gl text-justify break-word`} 1429 > 1430 {`${$ls( 1431 `eula.prohibited_conduct.body_li_0_${li}`, 1432 )}`} 1433 </p> 1434 </div> 1435 </div> 1436 {/each} 1437 </div> 1438 </div> 1439 <div 1440 class={`flex flex-col w-full gap-2 justify-start items-start`} 1441 > 1442 <p 1443 class={`font-mono font-[600] text-ly0-gl`} 1444 > 1445 {`**${$ls( 1446 `eula.consequences_of_violation.title`, 1447 )}**`} 1448 </p> 1449 <p 1450 class={`font-mono font-[500] text-ly0-gl text-sm text-justify break-word`} 1451 > 1452 {`${$ls( 1453 `eula.consequences_of_violation.body`, 1454 )}`} 1455 </p> 1456 </div> 1457 <div 1458 class={`flex flex-col w-full gap-2 justify-start items-start`} 1459 > 1460 <p 1461 class={`font-mono font-[600] text-ly0-gl`} 1462 > 1463 {`**${$ls(`eula.disclaimer.title`)}**`} 1464 </p> 1465 <p 1466 class={`font-mono font-[500] text-ly0-gl text-sm text-justify break-word`} 1467 > 1468 {`${$ls(`eula.disclaimer.body`)}`} 1469 </p> 1470 </div> 1471 <div 1472 class={`flex flex-col w-full gap-2 justify-start items-start`} 1473 > 1474 <p 1475 class={`font-mono font-[600] text-ly0-gl`} 1476 > 1477 {`**${$ls(`eula.changes.title`)}**`} 1478 </p> 1479 <p 1480 class={`font-mono font-[500] text-ly0-gl text-sm text-justify break-word`} 1481 > 1482 {`${$ls(`eula.changes.body`)}`} 1483 </p> 1484 </div> 1485 <div 1486 class={`flex flex-col w-full gap-2 justify-start items-start`} 1487 > 1488 <p 1489 class={`font-mono font-[600] text-ly0-gl`} 1490 > 1491 {`**${$ls(`eula.contact.title`)}**`} 1492 </p> 1493 <p 1494 class={`font-mono font-[500] text-ly0-gl text-sm text-justify break-word`} 1495 > 1496 {`${$ls(`eula.contact.body`)}`} 1497 </p> 1498 </div> 1499 <div 1500 class={`flex flex-col w-full gap-2 justify-start items-start`} 1501 > 1502 <p 1503 class={`font-mono font-[600] text-ly0-gl`} 1504 > 1505 {`**${$ls(`eula.acceptance_of_terms.title`)}**`} 1506 </p> 1507 <p 1508 class={`font-mono font-[500] text-ly0-gl text-sm text-justify break-word`} 1509 > 1510 {`${$ls(`eula.acceptance_of_terms.body`)}`} 1511 </p> 1512 </div> 1513 </div> 1514 </div> 1515 <div 1516 class={`flex flex-row w-full ios0:pt-8 pt-6 justify-center items-center`} 1517 > 1518 <button 1519 class={`group flex flex-row basis-1/2 gap-4 justify-center items-center ${ 1520 is_eula_scrolled ? `` : `opacity-80` 1521 }`} 1522 onclick={async () => { 1523 const confirm = await notif.confirm({ 1524 message: `${$ls( 1525 `eula.error.required`, 1526 )}`, 1527 cancel: `${$ls(`common.quit`)}`, 1528 }); 1529 1530 if (confirm === false) 1531 await page_reset(undefined, true); 1532 }} 1533 > 1534 <p 1535 class={`font-mono font-[400] text-sm text-ly0-gl group-active:text-ly0-gl el-re`} 1536 > 1537 {`-`} 1538 </p> 1539 <p 1540 class={`font-mono font-[400] text-sm text-ly0-gl group-active:text-ly0-gl el-re`} 1541 > 1542 {`${`${$ls(`common.disagree`)}`}`} 1543 </p> 1544 <p 1545 class={`font-mono font-[400] text-sm text-ly0-gl group-active:text-ly0-gl el-re`} 1546 > 1547 {`-`} 1548 </p> 1549 </button> 1550 <button 1551 class={`relative group flex flex-row basis-1/2 gap-4 justify-center items-center el-re ${ 1552 is_eula_scrolled ? `` : `opacity-40` 1553 }`} 1554 onclick={async () => { 1555 if (is_eula_scrolled) await submit(); 1556 }} 1557 > 1558 <p 1559 class={`font-mono font-[400] text-sm text-ly0-gl-hl group-active:text-ly0-gl-hl/80 el-re`} 1560 > 1561 {`-`} 1562 </p> 1563 <p 1564 class={`font-mono font-[400] text-sm text-ly0-gl-hl group-active:text-ly0-gl-hl/80 el-re`} 1565 > 1566 {`${`${$ls(`common.agree`)}`}`} 1567 </p> 1568 <p 1569 class={`font-mono font-[400] text-sm text-ly0-gl-hl group-active:text-ly0-gl-hl/80 el-re`} 1570 > 1571 {`- `} 1572 </p> 1573 {#if is_loading_s} 1574 <div 1575 class={`absolute right-3 flex flex-row justify-start items-center`} 1576 > 1577 <LoadSymbol basis={{ dim: `xs` }} /> 1578 </div> 1579 {/if} 1580 </button> 1581 </div> 1582 </div> 1583 </CarouselItem> 1584 </CarouselContainer> 1585 </div> 1586 </ViewPane> 1587 </ViewStack>