diff options
| author | Adam Soltys <[email protected]> | 2023-11-15 23:02:29 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-11-15 23:02:29 -0800 |
| commit | b466b3d39c3b9ec792ef924c7f4750fb70b6a016 (patch) | |
| tree | 9f0071056f7f53129e1eb81a1bca268300d41a05 /src/routes | |
| parent | 6d8eb8a609ebb6c1781c463a837bf9e19a6d3d4b (diff) | |
| download | coinos-ui-b466b3d39c3b9ec792ef924c7f4750fb70b6a016.tar.gz coinos-ui-b466b3d39c3b9ec792ef924c7f4750fb70b6a016.zip | |
prefill register page
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+layout.svelte | 36 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 8 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/+page.svelte | 28 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/_account.svelte | 6 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/_pos.svelte | 4 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/settings/[...tab]/_security.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/register/+page.server.js | 29 | ||||
| -rw-r--r-- | src/routes/(app)/register/+page.svelte | 245 | ||||
| -rw-r--r-- | src/routes/+layout.svelte | 3 | ||||
| -rw-r--r-- | src/routes/+page.server.js | 1 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 103 |
12 files changed, 383 insertions, 84 deletions
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index bcff6ef6..012dd957 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -98,7 +98,7 @@ {/if} {#if subject?.profile} - <meta name="og:image" content={`/api/public/${subject.uuid}-profile.webp`} /> + <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> {:else} <meta property="og:image" content="/icons/logo.svg" /> {/if} diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 1b99c4b4..bd2bf0d3 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -61,58 +61,66 @@ <div class="container mx-auto w-full px-4 mb-4 flex flex-wrap lg:flex-nowrap space-y-5"> <div class="hidden lg:block lg:w-[300px]" /> <div class="lg:w-[300px] lg:absolute space-y-5 left-20 mx-auto max-w-md"> - <div - class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" - on:click={toggleDetails} - > + <div class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"> <div class="my-auto">{display || username}</div> - <Icon icon="stats" style="w-12 rounded cursor-pointer" /> + <button on:click={toggleDetails}> + <Icon icon="stats" style="w-12" /> + </button> </div> {#if showDetails} <div> - <div class="font-bold">{$t('user.profilePage')}</div> + <div class="font-bold">{$t('user.profilePage')}</div> <div class="flex gap-2"> <div class="break-all grow text-secondary text-lg"> {profile} </div> <div class="flex mb-auto gap-1"> - <button class="my-auto opacity-60 hover:opacity-100" on:click={() => copy(profile)} + <button class="my-auto" on:click={() => copy(profile)} ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button > - <a href={`/qr/${encodeURIComponent(profile)}`} class="my-auto opacity-60 hover:opacity-100"> + <a + href={`/qr/${encodeURIComponent(profile)}`} + class="my-auto" + > <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> </a> </div> </div> </div> <div> - <div class="font-bold">{$t('user.lightningAddress')}</div> + <div class="font-bold">{$t('user.lightningAddress')}</div> <div class="flex gap-2"> <div class="break-all grow text-secondary text-lg"> {lnaddr} </div> <div class="flex mb-auto gap-1"> - <button class="my-auto opacity-60 hover:opacity-100" on:click={() => copy(lnaddr)} + <button class="my-auto" on:click={() => copy(lnaddr)} ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button > - <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto opacity-60 hover:opacity-100"> + <a + href={`/qr/${encodeURIComponent(lnaddr)}`} + class="my-auto" + > <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> </a> </div> </div> </div> <div> - <div class="font-bold">{$t('user.nostrPubkey')}</div> + <div class="font-bold">{$t('user.nostrPubkey')}</div> <div class="flex gap-2"> <div class="break-all grow text-secondary text-lg"> {npub} </div> <div class="flex my-auto gap-1"> - <button class="my-auto opacity-60 hover:opacity-100" on:click={() => copy(npub)} + <button class="my-auto" on:click={() => copy(npub)} ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button > - <a href={`/qr/${encodeURIComponent(npub)}`} class="my-auto opacity-60 hover:opacity-100"> + <a + href={`/qr/${encodeURIComponent(npub)}`} + class="my-auto" + > <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> </a> </div> diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index e3150ff2..42cf9527 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -96,20 +96,20 @@ <div class="flex flex-wrap gap-4 justify-center w-full"> <a href={`/${user.username}/receive`} class="w-full md:w-60"> <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" > <div class="mx-auto flex"> - <Icon icon="numpad" style="my-auto h-6 mr-2" /> + <Icon icon="numpad" style="my-auto h-6 mr-2 invert" /> <div class="my-auto mt-1">{$t('user.dashboard.receive')}</div> </div> </button> </a> <a href={`/send`} class="w-full md:w-60"> <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" > <div class="mx-auto flex"> - <Icon icon="send" style="my-auto h-6 mr-2" /> + <Icon icon="send" style="my-auto h-6 mr-2 invert" /> <div class="my-auto mt-1">{$t('user.dashboard.send')}</div> </div> </button> diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 762e836e..087c9450 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -68,13 +68,31 @@ } if ($avatar) { - await upload($avatar.file, $avatar.type, $avatar.progress, token); - await fetch(`/api/public/${id}-profile.webp`, { cache: 'reload', mode: 'no-cors' }); + try { + let { hash } = JSON.parse( + await upload($avatar.file, $avatar.type, $avatar.progress, token) + ); + + data.set('profile', hash); + + await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); + } catch (e) { + console.log('problem uploading avatar', e); + } } if ($banner) { - await upload($banner.file, $banner.type, $banner.progress, token); - await fetch(`/api/public/${id}-banner.webp`, { cache: 'reload', mode: 'no-cors' }); + try { + let { hash } = JSON.parse( + await upload($banner.file, $banner.type, $banner.progress, token) + ); + + data.set('banner', hash); + + await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); + } catch (e) { + console.log('problem uploading banner', e); + } } let event = { @@ -84,7 +102,7 @@ content: JSON.stringify({ name: user.username, about: user.address, - picture: `${PUBLIC_COINOS_URL}/public/${user.id}-profile.webp` + picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp` }), tags: [] }; diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte index 26c327c0..15d286c8 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte @@ -5,7 +5,7 @@ import { t } from '$lib/translations'; import { page } from '$app/stores'; - export let user; + export let user, rates, submit; let { id } = user; let selectedTheme = 1; @@ -95,7 +95,7 @@ on:keydown={selectAvatar} > <img - src={$avatar?.src || `/api/public/${user.id}-profile.webp`} + src={$avatar?.src || `/api/public/${user.profile}.webp`} class="absolute w-full h-full object-cover object-center visible overflow-hidden" alt={user.username} /> @@ -138,7 +138,7 @@ {#if $banner || user.banner} <img - src={$banner ? $banner.src : `/api/public/${user.id}-banner.webp`} + src={$banner ? $banner.src : `/api/public/${user.banner}.webp`} class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" on:click={selectBanner} on:keydown={selectBanner} diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 057d8c20..a96528a0 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -4,7 +4,7 @@ import { success, fail } from '$lib/utils'; import { page } from '$app/stores'; - export let user, rates; + export let user, rates, submit; let fiats = Object.keys(rates); </script> @@ -25,7 +25,7 @@ <div> <div class="flex justify-between items-center"> <span class="font-bold">{$t('user.settings.tipPrompt')}</span> - <Toggle id="prompt" checked={user.prompt} bind:value={user.prompt} /> + <Toggle id="prompt" bind:value={user.prompt} /> </div> <p class="text-secondary mt-1 w-9/12"> {$t('user.settings.tipPromptDescription')} diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte index 85f461ad..170df744 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte @@ -8,7 +8,7 @@ import { invalidate } from '$app/navigation'; import { page } from '$app/stores'; - export let user, submit; + export let user, rates, submit; let confirming2fa, disabling2fa, diff --git a/src/routes/(app)/register/+page.server.js b/src/routes/(app)/register/+page.server.js index c90d4ee3..f2b49e3f 100644 --- a/src/routes/(app)/register/+page.server.js +++ b/src/routes/(app)/register/+page.server.js @@ -1,9 +1,32 @@ +import { + NumberDictionary, + uniqueNamesGenerator, + colors, + adjectives, + animals +} from 'unique-names-generator'; import { fail, redirect } from '@sveltejs/kit'; import { fd, post, login } from '$lib/utils'; export const load = async ({ parent }) => { let { user } = await parent(); if (user) throw redirect(307, `/${user.username}`); + + let username = uniqueNamesGenerator({ + dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], + length: 2, + separator: '' + }); + + let password = uniqueNamesGenerator({ + dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], + length: 2, + separator: '' + }); + + let index = Math.floor(Math.random() * 64) + 1; + + return { index, username, password }; }; export const actions = { @@ -11,8 +34,8 @@ export const actions = { let ip = request.headers.get('cf-connecting-ip'); let form = await fd(request); - let { username, password, cipher, salt, pubkey, loginRedirect } = form; - let user = { username, password, cipher, salt, pubkey }; + let { profile, username, password, cipher, salt, pubkey, loginRedirect } = form; + let user = { profile, username, password, cipher, salt, pubkey }; let error; if (loginRedirect === 'undefined') loginRedirect = undefined; @@ -20,7 +43,7 @@ export const actions = { try { await post('/register', { user }, { 'cf-connecting-ip': ip }); } catch (e) { - console.log(e) + console.log(e); if (e.message.includes('taken')) error = e.message; } diff --git a/src/routes/(app)/register/+page.svelte b/src/routes/(app)/register/+page.svelte index fd9e4417..37ff67c8 100644 --- a/src/routes/(app)/register/+page.svelte +++ b/src/routes/(app)/register/+page.svelte @@ -1,7 +1,33 @@ <script> + import punks from '$lib/punks'; + import { upload } from '$lib/upload'; import { afterNavigate, invalidateAll } from '$app/navigation'; - import { RegisterOrLogin } from '$comp'; + import { applyAction, deserialize } from '$app/forms'; + import { tick } from 'svelte'; + import { fly } from 'svelte/transition'; + import { enhance } from '$app/forms'; + import { Pin, Icon, Spinner } from '$comp'; + import { focus, fail } from '$lib/utils'; + import { avatar, password, loginRedirect } from '$lib/store'; + import { t } from '$lib/translations'; + import { page } from '$app/stores'; + import { generate } from '$lib/nostr'; + export let form; + export let data; + + let cleared; + let clear = () => { + if (!cleared) { + cleared = true; + username = ''; + $password = ''; + revealPassword = false; + } + }; + + let { index, username } = data; + $password = data.password; afterNavigate(async () => { try { @@ -10,6 +36,221 @@ console.log(e); } }); + + let token, formElement; + let code = []; + let redirect; + + $: need2fa = form?.message === '2fa'; + let cancel = () => (need2fa = false); + $: if (form?.message === '2fa' && form.token === token) token = ''; + + let email, btn; + + $: update(form); + let update = (form) => form && ({ username, password: $password } = form); + + $: token && token?.length === 6 && tick().then(() => btn.click()); + + let revealPassword = true; + + let loading; + async function handleSubmit(e) { + loading = true; + + let data = new FormData(this); + let user = Object.fromEntries(data); + await generate(user); + + for (let k in user) { + data.set(k, user[k]); + } + + data.set('profile', punks[index]); + if ($avatar) { + try { + let { hash } = JSON.parse( + await upload($avatar.file, $avatar.type, $avatar.progress, token) + ); + + data.set('profile', hash); + await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); + } catch (e) { + console.log('problem uploading avatar', e); + } + } + + const response = await fetch('/register', { + method: 'POST', + body: data + }); + + const result = deserialize(await response.text()); + + if (result.type === 'success') { + await invalidateAll(); + } + + applyAction(result); + loading = false; + } + + let avatarInput; + $: src = `/api/public/${punks[index]}.webp`; + let decr = () => (index = index <= 1 ? 64 : index - 1); + let incr = () => (index = index >= 64 ? 1 : index + 1); + let selectAvatar = () => avatarInput.click(); + + let progress; + let handleFile = async ({ target }) => { + let type = 'profile'; + let file = target.files[0]; + if (!file) return; + + if (file.size > 10000000) form.error = 'File too large'; + $avatar = { file, type, progress }; + + var reader = new FileReader(); + reader.onload = async (e) => { + src = e.target.result; + }; + + reader.readAsDataURL(file); + }; </script> -<RegisterOrLogin pageID="register" bind:form /> +{#if need2fa} + <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> +{/if} + +<div class="pt-10"> + <div class="w-[243px] mx-auto mb-10"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> + + <div class="flex justify-center items-center"> + <div class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"> + <h1 class="text-2xl font-bold text-center">{$t('login.register')}</h1> + + <input + type="file" + class="hidden" + bind:this={avatarInput} + on:change={(e) => handleFile(e, 'profile')} + /> + + <div class="relative"> + <button class="absolute w-8 h-12 left-8 bg-white rounded top-12" on:click={decr}> + <Icon icon="chevron-left" style="w-8 " /> + </button> + <div class="relative w-36 mx-auto" on:click={selectAvatar}> + <button + class="w-36 h-36 rounded-full border-4 border-white overflow-hidden flex mx-auto relative" + > + <img + {src} + class="w-full h-full object-cover object-center overflow-hidden" + alt={username} + /> + </button> + <button + class="absolute bg-white rounded-full p-2 mx-auto right-0 bottom-0 z-10 bg-opacity-80" + > + <Icon icon="upload" style="w-8" /> + </button> + </div> + <button class="absolute w-8 h-12 right-8 bg-white rounded top-12" on:click={incr}> + <Icon icon="chevron-left" style="w-8 rotate-180" /> + </button> + </div> + + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} + + <form class="space-y-5" on:submit|preventDefault={handleSubmit} method="POST"> + <input + type="hidden" + name="loginRedirect" + value={$loginRedirect || $page.url.searchParams.get('redirect')} + /> + <input type="hidden" name="token" value={token} /> + + <div> + <label for="username" class="font-semibold">{$t('login.username')}</label> + <input + name="username" + type="text" + required + class="bg-primary" + bind:value={username} + on:focus={clear} + autocapitalize="none" + /> + </div> + + <div class="relative"> + <label for="password" class="block font-semibold">{$t('login.password')}</label> + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + on:focus={clear} + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" + > + <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> + </button> + </div> + + <p class="text-secondary text-sm"> + {$t('login.passwordRecommendation')} + </p> + + <button + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + bind:this={btn} + > + {#if loading} + <Spinner /> + {:else} + {$t('login.register')} + {/if} + </button> + </form> + + <p class="text-secondary text-center font-medium"> + {$t('login.haveAccount')} + <a + href={'/login'} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {$t('login.signIn')} + </a> + </p> + </div> + </div> +</div> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 7e9cb755..2cd0c473 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,5 +1,6 @@ <script> import '../app.css'; + import { loading, t } from '$lib/translations'; export let data; let { host, pathname } = data; @@ -26,7 +27,9 @@ <meta name="twitter:creator" content="@coinoswallet" /> </svelte:head> +{#if !$loading} <slot /> +{/if} <style global> :root { diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js index 7936e88b..9e4b62e2 100644 --- a/src/routes/+page.server.js +++ b/src/routes/+page.server.js @@ -8,5 +8,6 @@ export let load = async () => { data = await get('/locations'); } catch (e) {} + data.faqs = ['cost', 'compatibility', 'safety', 'pos', 'bitcoin']; return data; }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 0aaf887d..9d2ba213 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,11 +12,11 @@ import { t } from '$lib/translations'; import { close } from '$lib/socket'; - import { onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; export let data; - let { locations, user } = data; - let update = (data) => ({ locations, user } = data); + let { faqs, locations, user } = data; + let update = (data) => ({ faqs, locations, user } = data); $: update(data); let howItWorks; @@ -24,7 +24,6 @@ let about; let log = console.log; - let faqs = {}; const howItWorksSteps = [ { image: 'hand', stepID: 'step1' }, @@ -32,9 +31,9 @@ { image: 'smile', stepID: 'step3' } ]; - onMount(async () => { - faqs = (await import('../locales/en.json')).default.faq; + let loaded; + onMount(async () => { close(); try { @@ -42,62 +41,68 @@ } catch (e) { console.log(e); } + + loaded = true; }); + + onDestroy(() => (loaded = false)); </script> <svelte:head> <script src="https://stamen-maps.a.ssl.fastly.net/js/tile.stamen.js"></script> </svelte:head> -<LandingHeader {howItWorks} {faq} {about} {user} /> - -<main class="space-y-40 py-20 md:py-32 lg:py-36 xl:py-40 px-5 md:px-0"> - <LandingHero {user} /> - <LandingInfoCard - image="lightning-qr" - title={$t('landing.info1.title')} - description={$t('landing.info1.description')} - /> +{#if loaded} + <LandingHeader {howItWorks} {faq} {about} {user} /> - <LandingInfoCard image="phone-checkout" title={$t('landing.info2.title')} order="reverse"> - {$t('landing.info2.description1')} - <br /><br /> - {$t('landing.info2.description2')} - <br /><br /> - {@html $t('landing.info2.description3')} - </LandingInfoCard> - - <div> + <main class="space-y-40 py-20 md:py-32 lg:py-36 xl:py-40 px-5 md:px-0"> + <LandingHero {user} /> <LandingInfoCard - image="customize" - title={$t('landing.info3.title')} - description={$t('landing.info3.description')} + image="lightning-qr" + title={$t('landing.info1.title')} + description={$t('landing.info1.description')} /> - <div bind:this={howItWorks} /> - <div id="about" /> - </div> - <div> - <h3 class="text-5xl font-medium mb-10 text-center">{$t('howItWorks.header')}</h3> - <div class="grid lg:grid-cols-3 space-y-10 lg:space-y-0 text-center"> - {#each howItWorksSteps as step} - <HowItWorksCard image={step.image} stepID={step.stepID} /> - {/each} + <LandingInfoCard image="phone-checkout" title={$t('landing.info2.title')} order="reverse"> + {$t('landing.info2.description1')} + <br /><br /> + {$t('landing.info2.description2')} + <br /><br /> + {@html $t('landing.info2.description3')} + </LandingInfoCard> + + <div> + <LandingInfoCard + image="customize" + title={$t('landing.info3.title')} + description={$t('landing.info3.description')} + /> + <div bind:this={howItWorks} /> + <div id="about" /> + </div> + + <div> + <h3 class="text-5xl font-medium mb-10 text-center">{$t('howItWorks.header')}</h3> + <div class="grid lg:grid-cols-3 space-y-10 lg:space-y-0 text-center"> + {#each howItWorksSteps as step} + <HowItWorksCard image={step.image} stepID={step.stepID} /> + {/each} + </div> + <div bind:this={faq} /> </div> - <div bind:this={faq} /> - </div> - <div> - <div class="space-y-10"> - <h3 class="text-5xl font-medium text-center pt-40 -mt-40" id="faq">{$t('faq.header')}</h3> - {#each Object.keys(faqs).filter((f) => f !== 'header') as faqID} - <FaqCard questionID={faqID} /> - {/each} + <div> + <div class="space-y-10"> + <h3 class="text-5xl font-medium text-center pt-40 -mt-40" id="faq">{$t('faq.header')}</h3> + {#each faqs as f} + <FaqCard questionID={f} /> + {/each} + </div> + <div bind:this={about} /> </div> - <div bind:this={about} /> - </div> - <About {locations} /> -</main> + <About {locations} /> + </main> -<Footer /> + <Footer /> +{/if} |
