diff options
Diffstat (limited to 'apps/kit/src/routes/(main)')
| -rw-r--r-- | apps/kit/src/routes/(main)/(app)/+layout.svelte | 215 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/(app)/home/+page.svelte | 1 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/(public)/+layout.svelte | 1 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/(public)/login/+page.svelte | 136 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/(public)/reset/+page.svelte | 104 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/(public)/signup/+page.svelte | 38 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/+layout.server.ts | 13 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/+layout.svelte | 41 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/+layout.ts | 9 | ||||
| -rw-r--r-- | apps/kit/src/routes/(main)/+page.svelte | 2 |
10 files changed, 560 insertions, 0 deletions
diff --git a/apps/kit/src/routes/(main)/(app)/+layout.svelte b/apps/kit/src/routes/(main)/(app)/+layout.svelte new file mode 100644 index 0000000..3f60af3 --- /dev/null +++ b/apps/kit/src/routes/(main)/(app)/+layout.svelte @@ -0,0 +1,215 @@ +<svelte:options immutable={true}/> +<svelte:window bind:online={online}/> +<script lang="ts"> + import LL, {setLocale} from "$lib/i18n/i18n-svelte"; + import {Dialog, TransitionChild, TransitionRoot} from '@rgossiaux/svelte-headlessui'; + import {XIcon, MenuIcon, HomeIcon, DatabaseIcon, AdjustmentsIcon} from "$lib/components/icons"; + + let online = true; + let sidebarIsOpen = false; + const username = "dumb"; + + setLocale("nb"); + + const navigations = [ + { + name: "Home", + icon: HomeIcon + }, + { + name: "Data", + icon: DatabaseIcon + }, + { + name: "Settings", + icon: AdjustmentsIcon + } + ] +</script> +{#if !online} + <div class="bg-yellow-50 border-l-4 border-yellow-400 p-4"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" + fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" + d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" + clip-rule="evenodd"/> + </svg> + </div> + <div class="ml-3"> + <p class="text-sm text-yellow-700"> + You seem to be offline, please check your internet connection. + </p> + </div> + </div> + </div> +{/if} +<div class="h-full flex"> + <TransitionRoot show={sidebarIsOpen}> + <Dialog class="relative z-40 lg:hidden" on:close={() => sidebarIsOpen = !sidebarIsOpen}> + <TransitionChild + enter="transition-opacity ease-linear duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="transition-opacity ease-linear duration-300" + leaveFrom="opacity-100" + leaveTo="opacity-0"> + <div class="fixed inset-0 bg-gray-600 bg-opacity-75"></div> + </TransitionChild> + + <div class="fixed inset-0 flex z-40"> + <TransitionChild + enter="transition ease-in-out duration-300 transform" + enterFrom="-translate-x-full" + enterTo="translate-x-0" + leave="transition ease-in-out duration-300 transform" + leaveFrom="translate-x-0" + leaveTo="-translate-x-full"> + <DialogPanel class="relative flex-1 flex flex-col max-w-xs w-full bg-white focus:outline-none"> + <TransitionChild + enter="ease-in-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in-out duration-300" + leaveFrom="opacity-100" + leaveTo="opacity-0"> + <div class="absolute top-0 right-0 -mr-12 pt-2"> + <button type="button" + class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" + on:click={() => sidebarIsOpen = false}> + <span class="sr-only">Close sidebar</span> + <XIcon class="text-white" aria-hidden="true"/> + </button> + </div> + </TransitionChild> + <div class="flex-1 h-0 pt-5 pb-4 overflow-y-auto"> + <div class="flex-shrink-0 flex items-center px-4"> + <img class="h-8 w-auto" + src="https://tailwindui.com/img/logos/workflow-mark.svg?color=indigo&shade=600" + alt="Workflow" + /> + </div> + <nav aria-label="Sidebar" class="mt-5"> + <div class="px-2 space-y-1"> + {#each navigations as item (item.name)} + <a href={item.href} + class="{item.current? 'bg-gray-100 text-gray-900': 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'} group flex items-center px-2 py-2 text-base font-medium rounded-md"> + <svelte:component this="{item.icon}" + class="{item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'} mr-4 h-6 w-6" + aria-hidden="true"></svelte:component> + {item.name} + </a> + {/each} + </div> + </nav> + </div> + <div class="flex-shrink-0 flex border-t border-gray-200 p-4"> + <a href="#" class="flex-shrink-0 group block"> + <div class="flex items-center"> + <div> + <img class="inline-block h-10 w-10 rounded-full" + src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=256&h=256&q=80" + alt="" + /> + </div> + <div class="ml-3"> + <p class="text-base font-medium text-gray-700 group-hover:text-gray-900"> + {username} + </p> + <p class="text-sm font-medium text-gray-500 group-hover:text-gray-700"> + {$LL.nav.usermenu.profileTitle()} + </p> + </div> + </div> + </a> + </div> + </DialogPanel> + </TransitionChild> + <div class="flex-shrink-0 w-14" aria-hidden="true"> + <!--{/* Force sidebar to shrink to fit close icon */}--> + </div> + </div> + </Dialog> + </TransitionRoot> + + <!--{/* Static sidebar for desktop */}--> + <div class="hidden lg:flex lg:flex-shrink-0"> + <div class="flex flex-col w-64"> + <!--{/* Sidebar component, swap this element with another sidebar if you like */}--> + <div class="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-gray-100"> + <div class="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto"> + <div class="flex items-center flex-shrink-0 px-4"> + <img class="h-8 w-auto" + src="https://tailwindui.com/img/logos/workflow-mark.svg?color=indigo&shade=600" + alt="Workflow" + /> + </div> + <nav class="mt-5 flex-1" aria-label="Sidebar"> + <div class="px-2 space-y-1"> + {#each navigations as item (item.name)} + <a key={item.name} + href={item.href} + class="{item.current ? 'bg-gray-200 text-gray-900' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'} group flex items-center px-2 py-2 text-sm font-medium rounded-md"> + <svelte:component this="{item.icon}" + class="{item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'} mr-3 h-6 w-6" + aria-hidden="true"></svelte:component> + {item.name} + </a> + {/each} + </div> + </nav> + </div> + <div class="flex-shrink-0 flex border-t border-gray-200 p-4"> + <a href="#" class="flex-shrink-0 w-full group block"> + <div class="flex items-center"> + <div> + <img class="inline-block h-9 w-9 rounded-full" + src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=256&h=256&q=80" + alt="" + /> + </div> + <div class="ml-3"> + <p class="text-base font-medium text-gray-700 group-hover:text-gray-900"> + {username} + </p> + <p class="text-sm font-medium text-gray-500 group-hover:text-gray-700"> + {$LL.nav.usermenu.profileTitle()} + </p> + </div> + </div> + </a> + </div> + </div> + </div> + </div> + <div class="flex flex-col min-w-0 flex-1 overflow-hidden"> + <div class="lg:hidden"> + <div class="flex items-center justify-between bg-gray-50 border-b border-gray-200 px-4 py-1.5"> + <div> + <button type="button" + class="-mr-3 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900" + on:click={() => sidebarIsOpen = true}> + <span class="sr-only">Open sidebar</span> + <MenuIcon aria-hidden="true"/> + </button> + </div> + </div> + </div> + <div class="flex-1 relative z-0 flex overflow-hidden"> + <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none"> + <!-- + MAIN CONTENT + --> + <slot/> + </main> + <aside class="hidden relative xl:flex xl:flex-col flex-shrink-0 w-96 border-l border-gray-200 overflow-y-auto"> + <!--{/* Start secondary column (hidden on smaller screens) */} + <div class="absolute inset-0 py-6 px-4 sm:px-6 lg:px-8"> + <div class="h-full border-2 border-gray-200 border-dashed rounded-lg" /> + </div> + {/* End secondary column */}--> + </aside> + </div> + </div> +</div>
\ No newline at end of file diff --git a/apps/kit/src/routes/(main)/(app)/home/+page.svelte b/apps/kit/src/routes/(main)/(app)/home/+page.svelte new file mode 100644 index 0000000..247ee47 --- /dev/null +++ b/apps/kit/src/routes/(main)/(app)/home/+page.svelte @@ -0,0 +1 @@ +<h1>Welcome Home</h1>
\ No newline at end of file diff --git a/apps/kit/src/routes/(main)/(public)/+layout.svelte b/apps/kit/src/routes/(main)/(public)/+layout.svelte new file mode 100644 index 0000000..49aeb95 --- /dev/null +++ b/apps/kit/src/routes/(main)/(public)/+layout.svelte @@ -0,0 +1 @@ +<slot></slot>
\ No newline at end of file diff --git a/apps/kit/src/routes/(main)/(public)/login/+page.svelte b/apps/kit/src/routes/(main)/(public)/login/+page.svelte new file mode 100644 index 0000000..9e2f6e7 --- /dev/null +++ b/apps/kit/src/routes/(main)/(public)/login/+page.svelte @@ -0,0 +1,136 @@ +<script lang="ts"> + import { goto } from "$app/navigation"; + import { login } from "$lib/api/user"; + import LL from "$lib/i18n/i18n-svelte"; + import type { ErrorResult } from "$lib/models/ErrorResult"; + import type { LoginPayload } from "$lib/models/LoginPayload"; + + const data = { + username: "", + password: "", + } as LoginPayload; + + let error = { + text: "", + title: "", + } as ErrorResult; + + async function submitFormAsync() { + error = { text: "", title: "" }; + const loginResponse = await login(data); + if (loginResponse.ok) { + await goto("/home"); + } else { + error.title = loginResponse.data.title; + error.text = loginResponse.data.text; + } + } +</script> + +<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div class="sm:mx-auto sm:w-full sm:max-w-md"> + <h2 + class="mt-6 text-center text-3xl tracking-tight font-bold text-gray-900" + > + {$LL.login.loginToYourAccount()} + </h2> + <p class="mt-2 text-center text-sm text-gray-600"> + {$LL.common.or()} + <a + href="/signup" + class="font-medium text-indigo-600 hover:text-indigo-500" + >{$LL.login.createANewAccount()}</a + > + </p> + </div> + <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + {#if error.text || error.title} + <div class="rounded-md bg-red-50 p-3 mb-3"> + {#if error.title} + <h3 class="text-sm font-medium text-red-800"> + {error.title} + </h3> + {/if} + {#if error.text} + <div class="mt-2 text-sm text-red-700"> + {error.text} + </div> + {/if} + </div> + {/if} + <form class="space-y-6" on:submit|preventDefault={submitFormAsync}> + <div> + <label + for="email" + class="block text-sm font-medium text-gray-700" + >{$LL.common.emailAddress()}</label + > + <div class="mt-1"> + <input + id="email" + name="email" + type="email" + autocomplete="email" + required + value={data.username} + class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> + </div> + </div> + + <div> + <label + for="password" + class="block text-sm font-medium text-gray-700" + >{$LL.login.password()}</label + > + <div class="mt-1"> + <input + id="password" + name="password" + type="password" + autocomplete="current-password" + required + value={data.password} + class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> + </div> + </div> + + <div class="flex items-center justify-between"> + <div class="flex items-center"> + <input + id="remember-me" + name="remember-me" + type="checkbox" + class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" + /> + <label + for="remember-me" + class="ml-2 block text-sm text-gray-900" + >{$LL.login.notMyComputer()}</label + > + </div> + + <div class="text-sm"> + <a + href="/reset" + class="font-medium text-indigo-600 hover:text-indigo-500" + >{$LL.login.forgotPassword()}</a + > + </div> + </div> + + <div> + <button + type="submit" + class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" + > + {$LL.common.logIn()} + </button> + </div> + </form> + </div> + </div> +</div> diff --git a/apps/kit/src/routes/(main)/(public)/reset/+page.svelte b/apps/kit/src/routes/(main)/(public)/reset/+page.svelte new file mode 100644 index 0000000..5092b4b --- /dev/null +++ b/apps/kit/src/routes/(main)/(public)/reset/+page.svelte @@ -0,0 +1,104 @@ +<script lang="ts"> + import { create_forgot_password_request } from "$lib/api/user"; + import Alert from "$lib/components/alert.svelte"; + import LL from "$lib/i18n/i18n-svelte"; + import type { ErrorResult } from "$lib/models/ErrorResult"; + import { get } from "svelte/store"; + + const formData = { + email: "", + }; + + $: showErrorAlert = + (errorData?.text.length ?? 0 + errorData?.title.length ?? 0) > 0 && + !showSuccessAlert; + + const errorData = { + text: "", + title: "", + } as ErrorResult; + + let showSuccessAlert = false; + + async function submit() { + errorData.text = ""; + errorData.title = ""; + showSuccessAlert = false; + const request = await create_forgot_password_request(formData.email); + if (!request.ok) { + errorData.text = request.data.text ?? $LL.common.tryAgainSoon(); + errorData.title = request.data.title ?? $LL.common.unexpectedError(); + return; + } + showSuccessAlert = true; + } +</script> + +<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div class="sm:mx-auto sm:w-full sm:max-w-md"> + <h2 + class="mt-6 text-center text-3xl tracking-tight font-bold text-gray-900" + > + {$LL.reset.resetPassword()} + </h2> + <p class="mt-2 text-center text-sm text-gray-600"> + {$LL.common.or()} + <a + href="/login" + class="font-medium text-indigo-600 hover:text-indigo-500" + >{$LL.reset.gotoLoginPage().toLowerCase()}</a + > + </p> + </div> + + <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + <form + class="space-y-6" + on:submit|preventDefault={submit} + method="POST" + > + <Alert + title={errorData.title} + message={errorData.text} + type="error" + visible={showErrorAlert} + /> + + <Alert + type="success" + title={$LL.common.success()} + message={$LL.reset.requestSentMessage()} + visible={showSuccessAlert} + /> + <div> + <label + for="email" + class="block text-sm font-medium text-gray-700" + > + {$LL.common.emailAddress()}</label + > + <div class="mt-1"> + <input + id="email" + name="email" + type="email" + autocomplete="email" + required + bind:value={formData.email} + class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" + /> + </div> + </div> + <div> + <button + type="submit" + class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" + > + {$LL.common.send($LL.common.request().toLowerCase())} + </button> + </div> + </form> + </div> + </div> +</div> diff --git a/apps/kit/src/routes/(main)/(public)/signup/+page.svelte b/apps/kit/src/routes/(main)/(public)/signup/+page.svelte new file mode 100644 index 0000000..d4a1bda --- /dev/null +++ b/apps/kit/src/routes/(main)/(public)/signup/+page.svelte @@ -0,0 +1,38 @@ +<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8"> + <div class="sm:mx-auto sm:w-full sm:max-w-md"> + <h2 class="mt-6 text-center text-3xl tracking-tight font-bold text-gray-900">Create your new account</h2> + <p class="mt-2 text-center text-sm text-gray-600"> + Or + <a href="/login" class="font-medium text-indigo-600 hover:text-indigo-500">go to login page</a> + </p> + </div> + + <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> + <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> + <form class="space-y-6" action="#" method="POST"> + <div> + <label for="email" class="block text-sm font-medium text-gray-700"> Email address </label> + <div class="mt-1"> + <input id="email" name="email" type="email" autocomplete="email" required + class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> + </div> + </div> + + <div> + <label for="password" class="block text-sm font-medium text-gray-700"> Password </label> + <div class="mt-1"> + <input id="password" name="password" type="password" autocomplete="current-password" required + class="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> + </div> + </div> + + <div> + <button type="submit" + class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> + Create account + </button> + </div> + </form> + </div> + </div> +</div> diff --git a/apps/kit/src/routes/(main)/+layout.server.ts b/apps/kit/src/routes/(main)/+layout.server.ts new file mode 100644 index 0000000..01aae89 --- /dev/null +++ b/apps/kit/src/routes/(main)/+layout.server.ts @@ -0,0 +1,13 @@ +// import {is_active} from "$lib/session"; +// import {redirect} from "@sveltejs/kit"; +// import type {LayoutServerLoad} from "./$types"; +// +// export const load: LayoutServerLoad = async ({routeId}) => { +// const sessionIsValid = await is_active(); +// const isPublicRoute = routeId?.startsWith("(public)"); +// if (sessionIsValid && isPublicRoute) { +// throw redirect(302, "/home"); +// } else if (!sessionIsValid && !isPublicRoute) { +// throw redirect(302, "/login"); +// } +// };
\ No newline at end of file diff --git a/apps/kit/src/routes/(main)/+layout.svelte b/apps/kit/src/routes/(main)/+layout.svelte new file mode 100644 index 0000000..e5b177e --- /dev/null +++ b/apps/kit/src/routes/(main)/+layout.svelte @@ -0,0 +1,41 @@ +<script lang="ts"> + import "../../app.pcss"; + import { afterNavigate, beforeNavigate, goto } from "$app/navigation"; + import { is_active } from "$lib/session"; + import type { Navigation } from "@sveltejs/kit"; + import { setLocale } from "$lib/i18n/i18n-svelte"; + import { onMount } from "svelte"; + import type { LayoutData } from "./$types"; + import LocaleSwitcher from "$lib/components/locale-switcher.svelte"; + + export let data: LayoutData; + onMount(() => setLocale(data.locale)); + + async function redirect_if_necessary(ticket: Navigation) { + const sessionIsValid = await is_active(); + // TODO: ticket.to can be empty while navigating, so coalesce could probably cause non-public routes to cause a redir to /login... + const isPublicRoute = + (ticket.to?.routeId?.startsWith("(public)") ?? true) || + ticket.to?.routeId?.startsWith("book"); + + console.log("redir: ", { + isPublicRoute, + sessionIsValid, + }); + + if (sessionIsValid && isPublicRoute) { + await goto("/home"); + } else if (!sessionIsValid && !isPublicRoute) { + await goto("/login"); + } + } + + // This should probably be removed in favor of the logic in layout.server.ts. + // That requires a more sophisticated server side implementation of session handling, + // and i don't want that tbh, i want to stay as much in the browser as possible. + afterNavigate(redirect_if_necessary); + beforeNavigate(redirect_if_necessary); +</script> + +<LocaleSwitcher /> +<slot /> diff --git a/apps/kit/src/routes/(main)/+layout.ts b/apps/kit/src/routes/(main)/+layout.ts new file mode 100644 index 0000000..de8a5c0 --- /dev/null +++ b/apps/kit/src/routes/(main)/+layout.ts @@ -0,0 +1,9 @@ +import type {Locales} from "$lib/i18n/i18n-types"; +import {loadLocaleAsync} from "$lib/i18n/i18n-util.async"; +import type {LayoutLoad} from "./$types"; + +export const load: LayoutLoad<{ locale: Locales }> = async ({url, params}) => { + let lang = "en" as Locales; + await loadLocaleAsync(lang); + return {locale: lang}; +};
\ No newline at end of file diff --git a/apps/kit/src/routes/(main)/+page.svelte b/apps/kit/src/routes/(main)/+page.svelte new file mode 100644 index 0000000..85a4d2d --- /dev/null +++ b/apps/kit/src/routes/(main)/+page.svelte @@ -0,0 +1,2 @@ + +<p class="text-bold p-1">Hold on...</p> |
