diff options
Diffstat (limited to 'code/app/src/routes/(main)')
14 files changed, 607 insertions, 573 deletions
diff --git a/code/app/src/routes/(main)/(app)/+layout.svelte b/code/app/src/routes/(main)/(app)/+layout.svelte index a280fa7..6cb70ef 100644 --- a/code/app/src/routes/(main)/(app)/+layout.svelte +++ b/code/app/src/routes/(main)/(app)/+layout.svelte @@ -1,151 +1,162 @@ <script lang="ts"> - import { - ChevronUpDownIcon, - MagnifyingGlassIcon, - Bars3CenterLeftIcon, - XMarkIcon, - HomeIcon, - MegaphoneIcon, - FolderOpenIcon, - QueueListIcon, - CalendarIcon, - } from "$lib/components/icons"; - import { Dialog, Menu, MenuButton, MenuItem, MenuItems, Transition, TransitionChild, TransitionRoot } from "@rgossiaux/svelte-headlessui"; - import { DialogPanel } from "@developermuch/dev-svelte-headlessui"; - import { Input } from "$lib/components"; - import { end_session } from "$lib/session"; - import { goto } from "$app/navigation"; - import { page } from "$app/stores"; + import { + ChevronUpDownIcon, + MagnifyingGlassIcon, + Bars3CenterLeftIcon, + XMarkIcon, + HomeIcon, + MegaphoneIcon, + FolderOpenIcon, + QueueListIcon, + CalendarIcon, + } from "$components/icons"; + import {AccountService} from "$services/account-service"; + import { + Dialog, + Menu, + MenuButton, + MenuItem, + MenuItems, + Transition, + TransitionChild, + TransitionRoot, + } from "@rgossiaux/svelte-headlessui"; + import {DialogPanel} from "@developermuch/dev-svelte-headlessui"; + import {Input} from "$components"; + import {goto} from "$app/navigation"; + import {page} from "$app/stores"; + + const accountService = new AccountService(); - const session = { - profile: { - username: "Brukernavn", - displayName: "epost@adresse.no", - }, - }; + const session = { + profile: { + username: "Brukernavn", + displayName: "epost@adresse.no", + }, + }; - let sidebarOpen = false; - let sidebarSearchValue: string | undefined; + let sidebarOpen = false; + let sidebarSearchValue: string | undefined; - function sign_out() { - end_session(() => goto("/sign-in")); - } + function sign_out() { + accountService.end_session(() => goto("/sign-in")); + } - const navigationItems = [ - { - href: "/home", - name: "Home", - icon: HomeIcon, - }, - { - href: "/projects", - name: "Projects", - icon: CalendarIcon, - }, - { - href: "/tickets", - name: "Tickets", - icon: MegaphoneIcon, - }, - { - href: "/todo", - name: "Todo", - icon: QueueListIcon, - }, - { - href: "/wiki", - name: "Wiki", - icon: FolderOpenIcon, - }, - ]; + const navigationItems = [ + { + href: "/home", + name: "Home", + icon: HomeIcon, + }, + { + href: "/projects", + name: "Projects", + icon: CalendarIcon, + }, + { + href: "/tickets", + name: "Tickets", + icon: MegaphoneIcon, + }, + { + href: "/todo", + name: "Todo", + icon: QueueListIcon, + }, + { + href: "/wiki", + name: "Wiki", + icon: FolderOpenIcon, + }, + ]; </script> <div class="min-h-full"> - <!-- Mobile sidebar --> - <TransitionRoot show={sidebarOpen}> - <Dialog as="div" class="relative z-40 lg:hidden" on:close={() => (sidebarOpen = false)}> - <TransitionChild - as="div" - 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" /> - </TransitionChild> - - <div class="fixed inset-0 z-40 flex"> - <TransitionChild - as="div" - 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 w-full max-w-xs flex-1 flex-col bg-white pt-5 pb-4"> + <!-- Mobile sidebar --> + <TransitionRoot show={sidebarOpen}> + <Dialog as="div" class="relative z-40 lg:hidden" on:close={() => (sidebarOpen = false)}> <TransitionChild - as="div" - enter="ease-in-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in-out duration-300" - leaveFrom="opacity-100" - leaveTo="opacity-0" + as="div" + 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="absolute top-0 right-0 -mr-12 pt-2"> - <button - type="button" - class="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" - on:click={() => (sidebarOpen = false)} - > - <span class="sr-only">Close sidebar</span> - <XMarkIcon class="text-white" aria-hidden="true" /> - </button> - </div> + <div class="fixed inset-0 bg-gray-600 bg-opacity-75"/> </TransitionChild> - <div class="mt-5 h-0 flex-1 overflow-y-auto"> - <nav class="px-2"> - <div class="space-y-1"> - {#each navigationItems as item} - {@const current = $page.url.pathname.startsWith(item.href)} - <a - href={item.href} - aria-current={current ? "page" : undefined} - class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md + + <div class="fixed inset-0 z-40 flex"> + <TransitionChild + as="div" + 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 w-full max-w-xs flex-1 flex-col bg-white pt-5 pb-4"> + <TransitionChild + as="div" + 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 h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" + on:click={() => (sidebarOpen = false)} + > + <span class="sr-only">Close sidebar</span> + <XMarkIcon class="text-white" aria-hidden="true"/> + </button> + </div> + </TransitionChild> + <div class="mt-5 h-0 flex-1 overflow-y-auto"> + <nav class="px-2"> + <div class="space-y-1"> + {#each navigationItems as item} + {@const current = $page.url.pathname.startsWith(item.href)} + <a + href={item.href} + aria-current={current ? "page" : undefined} + class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md {current ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'}" - > - <svelte:component - this={item.icon} - class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" - aria-hidden="true" - /> - {item.name} - </a> - {/each} + > + <svelte:component + this={item.icon} + class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" + aria-hidden="true" + /> + {item.name} + </a> + {/each} + </div> + </nav> + </div> + </DialogPanel> + </TransitionChild> + <div class="w-14 flex-shrink-0" aria-hidden="true"> + <!-- Dummy element to force sidebar to shrink to fit close icon --> </div> - </nav> </div> - </DialogPanel> - </TransitionChild> - <div class="w-14 flex-shrink-0" aria-hidden="true"> - <!-- Dummy element to force sidebar to shrink to fit close icon --> - </div> - </div> - </Dialog> - </TransitionRoot> + </Dialog> + </TransitionRoot> - <!-- Static sidebar for desktop --> - <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col lg:border-r lg:border-gray-200 lg:bg-gray-100 lg:pb-4"> - <div class="flex h-0 flex-1 p-3 flex-col overflow-y-auto"> - <!-- User account dropdown --> - <Menu class="relative inline-block text-left"> - <MenuButton - class="group w-full rounded-md bg-gray-100 px-3.5 py-2 text-left text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-gray-100" - > + <!-- Static sidebar for desktop --> + <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col lg:border-r lg:border-gray-200 lg:bg-gray-100 lg:pb-4"> + <div class="flex h-0 flex-1 p-3 flex-col overflow-y-auto"> + <!-- User account dropdown --> + <Menu class="relative inline-block text-left"> + <MenuButton + class="group w-full rounded-md bg-gray-100 px-3.5 py-2 text-left text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-gray-100" + > <span class="flex w-full items-center justify-between"> <span class="flex min-w-0 items-center justify-between space-x-3"> <span class="flex min-w-0 flex-1 flex-col"> @@ -155,142 +166,151 @@ <span class="truncate text-sm text-gray-500">{session.profile.displayName}</span> </span> </span> - <ChevronUpDownIcon class="flex-shrink-0 text-gray-400 group-hover:text-gray-500" aria-hidden="true" /> + <ChevronUpDownIcon class="flex-shrink-0 text-gray-400 group-hover:text-gray-500" aria-hidden="true"/> </span> - </MenuButton> - <Transition - leave="transition ease-in duration-75" - enter="transition ease-out duration-100" - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - as="div" - > - <MenuItems - class="absolute right-0 left-0 z-10 mt-1 origin-top divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" - > - <div class="py-1"> - <MenuItem> - <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> View profile </a> - </MenuItem> - <MenuItem> - <a href="/settings" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> Settings </a> - </MenuItem> - </div> - <div class="py-1"> - <MenuItem> + </MenuButton> + <Transition + leave="transition ease-in duration-75" + enter="transition ease-out duration-100" + enterFrom="transform opacity-0 scale-95" + enterTo="transform opacity-100 scale-100" + leaveFrom="transform opacity-100 scale-100" + leaveTo="transform opacity-0 scale-95" + as="div" + > + <MenuItems + class="absolute right-0 left-0 z-10 mt-1 origin-top divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" + > + <div class="py-1"> + <MenuItem> + <a href="/profile" + class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> + View profile </a> + </MenuItem> + <MenuItem> + <a href="/settings" + class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> + Settings </a> + </MenuItem> + </div> + <div class="py-1"> + <MenuItem> <span - on:click={() => sign_out()} - class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer" + on:click={() => sign_out()} + class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer" > Sign out </span> - </MenuItem> + </MenuItem> + </div> + </MenuItems> + </Transition> + </Menu> + <!-- Sidebar Search --> + <div class="mt-3 hidden"> + <label for="search" class="sr-only">Search</label> + <div class="relative mt-1 rounded-md shadow-sm"> + <Input type="search" name="search" icon={MagnifyingGlassIcon} placeholder="Search" + bind:value={sidebarSearchValue}/> + </div> </div> - </MenuItems> - </Transition> - </Menu> - <!-- Sidebar Search --> - <div class="mt-3 hidden"> - <label for="search" class="sr-only">Search</label> - <div class="relative mt-1 rounded-md shadow-sm"> - <Input type="search" name="search" icon={MagnifyingGlassIcon} placeholder="Search" bind:value={sidebarSearchValue} /> - </div> - </div> - <!-- Navigation --> - <nav class="mt-5"> - <div class="space-y-1"> - {#each navigationItems as item} - {@const current = $page.url.pathname.startsWith(item.href)} - <a - href={item.href} - aria-current={current ? "page" : undefined} - class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md + <!-- Navigation --> + <nav class="mt-5"> + <div class="space-y-1"> + {#each navigationItems as item} + {@const current = $page.url.pathname.startsWith(item.href)} + <a + href={item.href} + aria-current={current ? "page" : undefined} + class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md {current ? 'bg-gray-200 text-gray-900' : 'text-gray-700 hover:text-gray-900 hover:bg-gray-50'}" - > - <svelte:component - this={item.icon} - class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" - aria-hidden="true" - /> - {item.name} - </a> - {/each} + > + <svelte:component + this={item.icon} + class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" + aria-hidden="true" + /> + {item.name} + </a> + {/each} + </div> + </nav> </div> - </nav> </div> - </div> - <!-- Main column --> - <div class="flex flex-col lg:pl-64"> - <!-- Search header --> - <div class="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:hidden"> - <button - type="button" - class="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden" - on:click={() => (sidebarOpen = true)} - > - <span class="sr-only">Open sidebar</span> - <Bars3CenterLeftIcon aria-hidden="true" /> - </button> - <div class="flex flex-1 justify-between px-4 sm:px-6 lg:px-8"> - <div class="flex flex-1"> - <form class="flex w-full md:ml-0" action="#" method="GET"> - <label for="search-field" class="sr-only">Search</label> - <div class="relative w-full text-gray-400 focus-within:text-gray-600"> - <Input - bind:value={sidebarSearchValue} - icon={MagnifyingGlassIcon} - id="search-field" - name="search-field" - placeholder="Search" - type="search" - /> - </div> - </form> - </div> - <div class="flex items-center"> - <!-- Profile dropdown --> - <Menu as="div" class="relative ml-3"> - <div> - <MenuButton - class="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2" - > - <span class="sr-only">Open user menu</span> - </MenuButton> - </div> - <Transition - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - as="div" + <!-- Main column --> + <div class="flex flex-col lg:pl-64"> + <!-- Search header --> + <div class="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:hidden"> + <button + type="button" + class="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden" + on:click={() => (sidebarOpen = true)} > - <MenuItems - class="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" - > - <div class="py-1"> - <MenuItem> - <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm"> View profile </a> - </MenuItem> - <MenuItem> - <a href="/settings" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> Settings </a> - </MenuItem> - <div class="py-1"> - <MenuItem> - <span on:click={() => sign_out()} class="text-gray-700 block px-4 py-2 text-sm"> Sign out </span> - </MenuItem> - </div> + <span class="sr-only">Open sidebar</span> + <Bars3CenterLeftIcon aria-hidden="true"/> + </button> + <div class="flex flex-1 justify-between px-4 sm:px-6 lg:px-8"> + <div class="flex flex-1"> + <form class="flex w-full md:ml-0" action="#" method="GET"> + <label for="search-field" class="sr-only">Search</label> + <div class="relative w-full text-gray-400 focus-within:text-gray-600"> + <Input + bind:value={sidebarSearchValue} + icon={MagnifyingGlassIcon} + id="search-field" + name="search-field" + placeholder="Search" + type="search" + /> + </div> + </form> </div> - </MenuItems> - </Transition> - </Menu> + <div class="flex items-center"> + <!-- Profile dropdown --> + <Menu as="div" class="relative ml-3"> + <div> + <MenuButton + class="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2" + > + <span class="sr-only">Open user menu</span> + </MenuButton> + </div> + <Transition + enterFrom="transform opacity-0 scale-95" + enterTo="transform opacity-100 scale-100" + leaveFrom="transform opacity-100 scale-100" + leaveTo="transform opacity-0 scale-95" + as="div" + > + <MenuItems + class="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" + > + <div class="py-1"> + <MenuItem> + <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm"> View + profile </a> + </MenuItem> + <MenuItem> + <a href="/settings" + class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> + Settings </a> + </MenuItem> + <div class="py-1"> + <MenuItem> + <span on:click={() => sign_out()} + class="text-gray-700 block px-4 py-2 text-sm"> Sign out </span> + </MenuItem> + </div> + </div> + </MenuItems> + </Transition> + </Menu> + </div> + </div> </div> - </div> + <main class="flex-1 p-3"> + <slot/> + </main> </div> - <main class="flex-1 p-3"> - <slot /> - </main> - </div> </div> diff --git a/code/app/src/routes/(main)/(app)/projects/+page.svelte b/code/app/src/routes/(main)/(app)/projects/+page.svelte index e39a886..1508118 100644 --- a/code/app/src/routes/(main)/(app)/projects/+page.svelte +++ b/code/app/src/routes/(main)/(app)/projects/+page.svelte @@ -1,137 +1,143 @@ <script lang="ts"> - import { Button, ProjectStatusBadge, Input } from "$lib/components"; - import type { Project } from "$lib/models/projects/Project"; - import { onMount } from "svelte"; - import { faker } from "@faker-js/faker"; - import { Temporal } from "temporal-polyfill"; - import { createTable, Subscribe, Render } from "svelte-headless-table"; - import { addSortBy, addTableFilter } from "svelte-headless-table/plugins"; - import { ProjectStatus } from "$lib/models/projects/ProjectStatus"; - import { writable, type Writable } from "svelte/store"; - import { ChevronDownIcon, ChevronUpIcon, ChevronUpDownIcon, MagnifyingGlassIcon, FunnelIcon } from "$lib/components/icons"; - import LL from "$lib/i18n/i18n-svelte"; - import { goto } from "$app/navigation"; + import {Button, ProjectStatusBadge, Input} from "$components"; + import type {Project} from "$models/projects/Project"; + import {onMount} from "svelte"; + import {faker} from "@faker-js/faker"; + import {Temporal} from "temporal-polyfill"; + import {createTable, Subscribe, Render} from "svelte-headless-table"; + import {addSortBy, addTableFilter} from "svelte-headless-table/plugins"; + import {ProjectStatus} from "$models/projects/ProjectStatus"; + import {writable, type Writable} from "svelte/store"; + import { + ChevronDownIcon, + ChevronUpIcon, + ChevronUpDownIcon, + MagnifyingGlassIcon, + FunnelIcon, + } from "$components/icons"; + import LL from "$i18n/i18n-svelte"; + import {goto} from "$app/navigation"; - let projects: Writable<Array<Project>> = writable([]); + let projects: Writable<Array<Project>> = writable([]); - onMount(() => { - let i = 0; - const tempProjects = []; - while (i < 101) { - tempProjects.push({ - id: crypto.randomUUID(), - name: faker.lorem.word(), - start: Temporal.Now.plainDateISO().toLocaleString(), - description: faker.lorem.words(3), - members: [], - status: ProjectStatus.IDLE, - }); - i++; - } - projects.set(tempProjects); - }); + onMount(() => { + let i = 0; + const tempProjects = []; + while (i < 101) { + tempProjects.push({ + id: crypto.randomUUID(), + name: faker.lorem.word(), + start: Temporal.Now.plainDateISO().toLocaleString(), + description: faker.lorem.words(3), + members: [], + status: ProjectStatus.IDLE, + }); + i++; + } + projects.set(tempProjects); + }); - function on_open_project(event) { - if (event.code && (event.code !== "Enter" || event.code !== "Space")) return; - const name = event.target.innerText; - const projectId = $projects.find((p) => p.name === name).id; - goto("/projects/" + projectId); - } + function on_open_project(event) { + if (event.code && (event.code !== "Enter" || event.code !== "Space")) return; + const name = event.target.innerText; + const projectId = $projects.find((p) => p.name === name).id; + goto("/projects/" + projectId); + } - const table = createTable(projects, { - sort: addSortBy(), - filter: addTableFilter(), - }); + const table = createTable(projects, { + sort: addSortBy(), + filter: addTableFilter(), + }); - const columns = table.createColumns([ - table.column({ header: $LL.name(), accessor: "name" }), - table.column({ header: "Status", accessor: "status" }), - table.column({ header: "Start", accessor: "start" }), - table.column({ header: "Description", accessor: "description", plugins: { sort: { disable: true } } }), - ]); + const columns = table.createColumns([ + table.column({header: $LL.name(), accessor: "name"}), + table.column({header: "Status", accessor: "status"}), + table.column({header: "Start", accessor: "start"}), + table.column({header: "Description", accessor: "description", plugins: {sort: {disable: true}}}), + ]); - const { headerRows, rows, tableAttrs, tableBodyAttrs, pluginStates } = table.createViewModel(columns); - const { filterValue } = pluginStates.filter; + const {headerRows, rows, tableAttrs, tableBodyAttrs, pluginStates} = table.createViewModel(columns); + const {filterValue} = pluginStates.filter; </script> <div class="sm:flex sm:items-center"> - <div class="sm:flex-auto"> - <h1 class="text-xl font-semibold text-gray-900">Projects</h1> - <p class="mt-2 text-sm text-gray-700">A list of all the projects in your organsation.</p> - </div> - <div class="mt-4 sm:mt-0 sm:ml-16 inline-flex gap-1 sm:flex-none"> - <Input icon={MagnifyingGlassIcon} placeholder="Search" bind:value={$filterValue} /> - <Button text="Create project" href="/projects/create" /> - </div> + <div class="sm:flex-auto"> + <h1 class="text-xl font-semibold text-gray-900">Projects</h1> + <p class="mt-2 text-sm text-gray-700">A list of all the projects in your organsation.</p> + </div> + <div class="mt-4 sm:mt-0 sm:ml-16 inline-flex gap-1 sm:flex-none"> + <Input icon={MagnifyingGlassIcon} placeholder="Search" bind:value={$filterValue}/> + <Button text="Create project" href="/projects/create"/> + </div> </div> <div class="-mx-2 mt-6 rounded-md shadow overflow-auto max-h-[80vh] sm:-mx-6 md:mx-0"> - <table {...$tableAttrs} class="min-w-full divide-y divide-gray-300"> - <thead class="bg-gray-50"> - {#each $headerRows as headerRow (headerRow.id)} - <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs> - <tr {...rowAttrs} class="shadow-sm"> - {#each headerRow.cells as cell (cell.id)} - <Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props> - <th - {...attrs} - scope="col" - class="sticky top-0 bg-gray-50 bg-opacity-100 whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900" - > - <div class="group inline-flex"> - <Render of={cell.render()} /> - <span - on:click={props.sort.toggle} - on:keypress={props.sort.toggle} - class="{props.sort.disabled + <table {...$tableAttrs} class="min-w-full divide-y divide-gray-300"> + <thead class="bg-gray-50"> + {#each $headerRows as headerRow (headerRow.id)} + <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs> + <tr {...rowAttrs} class="shadow-sm"> + {#each headerRow.cells as cell (cell.id)} + <Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props> + <th + {...attrs} + scope="col" + class="sticky top-0 bg-gray-50 bg-opacity-100 whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900" + > + <div class="group inline-flex"> + <Render of={cell.render()}/> + <span + on:click={props.sort.toggle} + on:keypress={props.sort.toggle} + class="{props.sort.disabled ? 'bg-gray-200 text-gray-900 group-hover:bg-gray-300' : 'invisible text-gray-400 group-hover:visible group-focus:visible'} {props.sort.disabled ? '' : 'cursor-pointer'} ml-2 flex-none rounded" - > + > {#if props.sort.order === "asc"} - <ChevronUpIcon /> + <ChevronUpIcon/> {:else if props.sort.order === "desc"} - <ChevronDownIcon /> + <ChevronDownIcon/> {:else if !props.sort.disabled} - <ChevronUpDownIcon /> + <ChevronUpDownIcon/> {/if} </span> - {#if cell.id === "status"} + {#if cell.id === "status"} <span class="invisible text-gray-400 cursor-pointer group-hover:visible group-focus:visible ml-2 flex-none rounded"> - <FunnelIcon /> + <FunnelIcon/> </span> - {/if} - </div> - </th> - </Subscribe> - {/each} - </tr> - </Subscribe> - {/each} - </thead> - <tbody {...$tableBodyAttrs} class="divide-y divide-gray-200 bg-white"> - {#each $rows as row (row.id)} - <Subscribe rowAttrs={row.attrs()} let:rowAttrs> - <tr {...rowAttrs}> - {#each row.cells as cell (cell.id)} - {@const materialisedCell = cell.render()} - <Subscribe attrs={cell.attrs()} let:attrs> - <td {...attrs} class="whitespace-nowrap px-2 py-2 text-sm"> - {#if cell.id === "name"} + {/if} + </div> + </th> + </Subscribe> + {/each} + </tr> + </Subscribe> + {/each} + </thead> + <tbody {...$tableBodyAttrs} class="divide-y divide-gray-200 bg-white"> + {#each $rows as row (row.id)} + <Subscribe rowAttrs={row.attrs()} let:rowAttrs> + <tr {...rowAttrs}> + {#each row.cells as cell (cell.id)} + {@const materialisedCell = cell.render()} + <Subscribe attrs={cell.attrs()} let:attrs> + <td {...attrs} class="whitespace-nowrap px-2 py-2 text-sm"> + {#if cell.id === "name"} <span class="link" title="Open project" on:click={on_open_project} on:keypress={on_open_project}> - <Render of={materialisedCell} /> + <Render of={materialisedCell}/> </span> - {:else if cell.id === "status"} - <ProjectStatusBadge status={materialisedCell.toString()} /> - {:else} - <Render of={materialisedCell} /> - {/if} - </td> - </Subscribe> - {/each} - </tr> - </Subscribe> - {/each} - </tbody> - </table> + {:else if cell.id === "status"} + <ProjectStatusBadge status={materialisedCell.toString()}/> + {:else} + <Render of={materialisedCell}/> + {/if} + </td> + </Subscribe> + {/each} + </tr> + </Subscribe> + {/each} + </tbody> + </table> </div> diff --git a/code/app/src/routes/(main)/(app)/projects/create/+page.svelte b/code/app/src/routes/(main)/(app)/projects/create/+page.svelte index 1741506..ab38b2e 100644 --- a/code/app/src/routes/(main)/(app)/projects/create/+page.svelte +++ b/code/app/src/routes/(main)/(app)/projects/create/+page.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import { useSWR } from "sswr"; - import { Input, TextArea, Combobox, Button } from "$lib/components"; - import type { ProjectMember } from "$lib/models/projects/ProjectMember"; - import LL from "$lib/i18n/i18n-svelte"; + import {useSWR} from "sswr"; + import {Input, TextArea, Combobox, Button} from "$components"; + import type {ProjectMember} from "$models/projects/ProjectMember"; + import LL from "$i18n/i18n-svelte"; const formData = { name: { @@ -36,16 +36,16 @@ alert("Submitted"); } - const { data: members } = useSWR("projectMembers"); + const {data: members} = useSWR("projectMembers"); </script> <h1>Create a new project</h1> <form on:submit|preventDefault={submit_form_async} class="max-w-md flex flex-col gap-2"> - <Input label="Name" bind:value={formData.name.value} errors={formData.name.errors} required /> - <TextArea label="Description" bind:value={formData.description.value} errors={formData.description.errors} /> + <Input label="Name" bind:value={formData.name.value} errors={formData.name.errors} required/> + <TextArea label="Description" bind:value={formData.description.value} errors={formData.description.errors}/> <section class="grid grid-flow-row sm:grid-flow-col gap-2"> - <Input type="date" label="Start" bind:value={formData.start.value} errors={formData.start.errors} /> - <Input type="date" label="Stop" bind:value={formData.stop.value} errors={formData.stop.errors} /> + <Input type="date" label="Start" bind:value={formData.start.value} errors={formData.start.errors}/> + <Input type="date" label="Stop" bind:value={formData.stop.value} errors={formData.stop.errors}/> </section> <Combobox options={$members} label={$LL.app.members()}> <svelte:fragment slot="no-records"> @@ -57,5 +57,5 @@ {/if} </svelte:fragment> </Combobox> - <Button text={$LL.submit()} /> + <Button text={$LL.submit()}/> </form> diff --git a/code/app/src/routes/(main)/(app)/settings/+page.svelte b/code/app/src/routes/(main)/(app)/settings/+page.svelte index 1f0cc67..1c9a910 100644 --- a/code/app/src/routes/(main)/(app)/settings/+page.svelte +++ b/code/app/src/routes/(main)/(app)/settings/+page.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { Input, Button, Switch } from "$lib/components"; + import {Input, Button, Switch} from "$components"; </script> <div class="relative mx-auto max-w-4xl md:px-8 xl:px-0"> @@ -13,9 +13,9 @@ <div class="lg:hidden"> <label for="selected-tab" class="sr-only">Select a tab</label> <select - id="selected-tab" - name="selected-tab" - class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm" + id="selected-tab" + name="selected-tab" + class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm" > <option selected>General</option> @@ -34,38 +34,39 @@ <div class="border-b border-gray-200"> <nav class="-mb-px flex space-x-8"> <!-- Current: "border-purple-500 text-purple-600", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" --> - <a href="#" class="border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >General</a + <a href="#" + class="border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >General</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Password</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Password</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Notifications</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Notifications</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Plan</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Plan</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Billing</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Billing</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Team Members</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Team Members</a > </nav> </div> @@ -87,9 +88,9 @@ <span class="flex-grow">Chelsea Hagon</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -99,22 +100,22 @@ <dd class="mt-1 flex text-sm text-gray-900 sm:col-span-2 sm:mt-0"> <span class="flex-grow"> <img - class="h-8 w-8 rounded-full" - src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" - alt="" + class="h-8 w-8 rounded-full" + src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" + alt="" /> </span> <span class="ml-4 flex flex-shrink-0 items-start space-x-4"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > <span class="text-gray-300" aria-hidden="true">|</span> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Remove</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Remove</button > </span> </dd> @@ -125,9 +126,9 @@ <span class="flex-grow">chelsea.hagon@example.com</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -138,9 +139,9 @@ <span class="flex-grow">Human Resources Manager</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -152,7 +153,8 @@ <div class="mt-10 divide-y divide-gray-200"> <div class="space-y-1"> <h3 class="text-lg font-medium leading-6 text-gray-900">Account</h3> - <p class="max-w-2xl text-sm text-gray-500">Manage how information is displayed on your account.</p> + <p class="max-w-2xl text-sm text-gray-500">Manage how information is displayed on your + account.</p> </div> <div class="mt-6"> <dl class="divide-y divide-gray-200"> @@ -162,9 +164,9 @@ <span class="flex-grow">English</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -175,22 +177,24 @@ <span class="flex-grow">DD-MM-YYYY</span> <span class="ml-4 flex flex-shrink-0 items-start space-x-4"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > <span class="text-gray-300" aria-hidden="true">|</span> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Remove</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Remove</button > </span> </dd> </div> <div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:pt-5"> - <dt class="text-sm font-medium text-gray-500" id="timezone-option-label">Automatic timezone</dt> - <Switch /> + <dt class="text-sm font-medium text-gray-500" id="timezone-option-label">Automatic + timezone + </dt> + <Switch/> </div> </dl> </div> diff --git a/code/app/src/routes/(main)/(public)/+layout.svelte b/code/app/src/routes/(main)/(public)/+layout.svelte index cbda776..0d84f9a 100644 --- a/code/app/src/routes/(main)/(public)/+layout.svelte +++ b/code/app/src/routes/(main)/(public)/+layout.svelte @@ -1,10 +1,10 @@ <script> - import { LocaleSwitcher } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; + import {LocaleSwitcher} from "$components"; + import LL from "$i18n/i18n-svelte"; </script> -<LocaleSwitcher tabindex={-1} /> -<slot /> +<LocaleSwitcher tabindex={-1}/> +<slot/> <footer class="grid sm:gap-5 grid-flow-row sm:justify-center px-2 sm:grid-flow-col"> <a href="https://greatoffice.life/privacy" class="link"> {$LL.privacyPolicy()} diff --git a/code/app/src/routes/(main)/(public)/reset-password/+page.svelte b/code/app/src/routes/(main)/(public)/reset-password/+page.svelte index 2b34dfc..34dabae 100644 --- a/code/app/src/routes/(main)/(public)/reset-password/+page.svelte +++ b/code/app/src/routes/(main)/(public)/reset-password/+page.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import { Alert, Input, Button } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; - import { FormError } from "$lib/models/internal/FormError"; - import { PasswordResetService } from "$lib/services/password-reset-service"; + import {Alert, Input, Button} from "$components"; + import LL from "$i18n/i18n-svelte"; + import {FormError} from "$models/internal/FormError"; + import {PasswordResetService} from "$services/password-reset-service"; const formData = { email: { @@ -61,21 +61,21 @@ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> <form class="space-y-6" on:submit|preventDefault={submit_form_async}> {#if showErrorAlert} - <Alert title={formError.title} message={formError.subtitle} type="error" /> + <Alert title={formError.title} message={formError.subtitle} type="error"/> {:else if showSuccessAlert} - <Alert type="success" title={$LL.success()} message={$LL.resetPasswordPage.requestSentMessage()} /> + <Alert type="success" title={$LL.success()} message={$LL.resetPasswordPage.requestSentMessage()}/> {/if} <Input - id="email" - name="email" - type="email" - autocomplete="email" - errors={formData.email.errors} - bind:value={formData.email.value} - required - label={$LL.emailAddress()} + id="email" + name="email" + type="email" + autocomplete="email" + errors={formData.email.errors} + bind:value={formData.email.value} + required + label={$LL.emailAddress()} /> - <Button text={$LL.submit()} type="submit" {loading} fullWidth /> + <Button text={$LL.submit()} type="submit" {loading} fullWidth/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts index 389d04c..907b444 100644 --- a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts +++ b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts @@ -1,11 +1,11 @@ -import { is_guid } from '$lib/helpers'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import {is_guid} from "$help"; +import {redirect} from "@sveltejs/kit"; +import type {PageServerLoad} from "./$types"; -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({params}) => { const resetRequestId = params.id ?? ""; if (!is_guid(resetRequestId)) throw redirect(302, "/reset-password"); return { - resetRequestId + resetRequestId, }; };
\ No newline at end of file diff --git a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte index ba59a8f..8f817bf 100644 --- a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte +++ b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte @@ -1,11 +1,11 @@ <script lang="ts"> - import { onMount } from "svelte"; - import LL from "$lib/i18n/i18n-svelte"; - import { Alert, Input, Button } from "$lib/components"; - import type { PageServerData } from "./$types"; - import { goto } from "$app/navigation"; - import { SignInPageMessage, signInPageMessageQueryKey } from "$routes/(main)/(public)/sign-in"; - import { PasswordResetService } from "$lib/services/password-reset-service"; + import {onMount} from "svelte"; + import LL from "$i18n/i18n-svelte"; + import {Alert, Input, Button} from "$components"; + import type {PageServerData} from "./$types"; + import {goto} from "$app/navigation"; + import {SignInPageMessage, signInPageMessageQueryKey} from "$routes/(main)/(public)/sign-in"; + import {PasswordResetService} from "$services/password-reset-service"; export let data: PageServerData; const passwordResets = new PasswordResetService(); @@ -57,18 +57,19 @@ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> <form class="space-y-6" on:submit|preventDefault={submitFormAsync}> {#if requestIsInvalid} - <Alert title={$LL.resetPasswordPage.invalidRequestTitle()} message={$LL.resetPasswordPage.invalidRequestMessage()} /> + <Alert title={$LL.resetPasswordPage.invalidRequestTitle()} + message={$LL.resetPasswordPage.invalidRequestMessage()}/> {/if} <Input - id="password" - name="password" - type="password" - autocomplete="new-password" - required - bind:value={formData.newPassword.value} - label={$LL.resetPasswordPage.newPassword()} + id="password" + name="password" + type="password" + autocomplete="new-password" + required + bind:value={formData.newPassword.value} + label={$LL.resetPasswordPage.newPassword()} /> - <Button text={$LL.submit()} type="submit" {loading} fullWidth /> + <Button text={$LL.submit()} type="submit" {loading} fullWidth/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/(public)/sign-in/+page.svelte b/code/app/src/routes/(main)/(public)/sign-in/+page.svelte index 12801c5..e862050 100644 --- a/code/app/src/routes/(main)/(public)/sign-in/+page.svelte +++ b/code/app/src/routes/(main)/(public)/sign-in/+page.svelte @@ -1,13 +1,13 @@ <script lang="ts"> - import { goto } from "$app/navigation"; - import { Button, Checkbox, Input, Alert } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; + import {goto} from "$app/navigation"; + import {Button, Checkbox, Input, Alert} from "$components"; + import LL from "$i18n/i18n-svelte"; import pwKey from "$actions/pwKey"; - import { onMount } from "svelte"; - import { signInPageMessageQueryKey, signInPageTestKeys, type SignInPageMessage } from "."; - import { AccountService } from "$lib/services/account-service"; - import type { LoginPayload } from "$lib/services/abstractions/IAccountService"; - import { FormError } from "$lib/models/internal/FormError"; + import {onMount} from "svelte"; + import {signInPageMessageQueryKey, signInPageTestKeys, type SignInPageMessage} from "."; + import {AccountService} from "$services/account-service"; + import type {LoginPayload} from "$services/abstractions/IAccountService"; + import {FormError} from "$models/internal/FormError"; let loading = false; let showErrorAlert = false; @@ -71,24 +71,24 @@ <div class="sm:max-w-md sm:mx-auto sm:w-full"> {#if messageType === "after-password-reset"} <Alert - title={$LL.signInPage.yourNewPasswordIsApplied()} - _pwKey={signInPageTestKeys.afterPasswordResetAlert} - message={$LL.signInPage.signInBelow()} - closeable + title={$LL.signInPage.yourNewPasswordIsApplied()} + _pwKey={signInPageTestKeys.afterPasswordResetAlert} + message={$LL.signInPage.signInBelow()} + closeable /> {:else if messageType === "user-disabled"} <Alert - title={$LL.signInPage.yourAccountIsDisabled()} - _pwKey={signInPageTestKeys.userDisabledAlert} - message={$LL.signInPage.contactYourAdminIfDisabled()} - closeable + title={$LL.signInPage.yourAccountIsDisabled()} + _pwKey={signInPageTestKeys.userDisabledAlert} + message={$LL.signInPage.contactYourAdminIfDisabled()} + closeable /> {:else if messageType === "user-inactivity"} <Alert - title={$LL.signInPage.youHaveReachedInactivityLimit()} - _pwKey={signInPageTestKeys.userInactivityAlert} - message={$LL.signInPage.feelFreeToSignInAgain()} - closeable + title={$LL.signInPage.youHaveReachedInactivityLimit()} + _pwKey={signInPageTestKeys.userInactivityAlert} + message={$LL.signInPage.feelFreeToSignInAgain()} + closeable /> {/if} </div> @@ -107,37 +107,39 @@ <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 showErrorAlert} - <Alert title={formError.title} message={formError.subtitle} type="error" _pwKey={signInPageTestKeys.formErrorAlert} /> + <Alert title={formError.title} message={formError.subtitle} type="error" + _pwKey={signInPageTestKeys.formErrorAlert}/> {/if} - <form class="space-y-6 mt-2" use:pwKey={signInPageTestKeys.signInForm} on:submit|preventDefault={submit_form_async}> + <form class="space-y-6 mt-2" use:pwKey={signInPageTestKeys.signInForm} + on:submit|preventDefault={submit_form_async}> <Input - id="username" - _pwKey={signInPageTestKeys.usernameInput} - name="username" - type="email" - label={$LL.emailAddress()} - required - bind:value={formData.username.value} + id="username" + _pwKey={signInPageTestKeys.usernameInput} + name="username" + type="email" + label={$LL.emailAddress()} + required + bind:value={formData.username.value} /> <Input - id="password" - name="password" - type="password" - label={$LL.password()} - _pwKey={signInPageTestKeys.passwordInput} - autocomplete="current-password" - required - bind:value={formData.password.value} + id="password" + name="password" + type="password" + label={$LL.password()} + _pwKey={signInPageTestKeys.passwordInput} + autocomplete="current-password" + required + bind:value={formData.password.value} /> <div class="flex items-center justify-between"> <Checkbox - id="remember-me" - _pwKey={signInPageTestKeys.rememberMeCheckbox} - name="remember-me" - bind:checked={formData.persist.value} - label={$LL.signInPage.notMyComputer()} + id="remember-me" + _pwKey={signInPageTestKeys.rememberMeCheckbox} + name="remember-me" + bind:checked={formData.persist.value} + label={$LL.signInPage.notMyComputer()} /> <div class="text-sm"> <a href="/reset-password" class="link" use:pwKey={signInPageTestKeys.resetPasswordAnchor}> @@ -146,7 +148,7 @@ </div> </div> - <Button text={$LL.submit()} fullWidth type="submit" {loading} /> + <Button text={$LL.submit()} fullWidth type="submit" {loading}/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts b/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts index ea8c494..9a9f7a5 100644 --- a/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts +++ b/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts @@ -1,12 +1,12 @@ -import { test, expect } from "@playwright/test"; -import { signInPageTestKeys } from "../index"; -import { get_test_context } from "$lib/configuration"; -import { get_pw_key_selector } from "$lib/helpers"; +import {test, expect} from "@playwright/test"; +import {signInPageTestKeys} from "../index"; +import {get_test_context} from "$configuration"; +import {get_pw_key_selector} from "$help"; const context = get_test_context(); -test("form loads", async ({ page }) => { +test("form loads", async ({page}) => { page.goto("/sign-in"); const form = page.locator(get_pw_key_selector(signInPageTestKeys.signInForm)); expect(form.isVisible()).toBeTruthy(); -}) +}); diff --git a/code/app/src/routes/(main)/(public)/sign-up/+page.svelte b/code/app/src/routes/(main)/(public)/sign-up/+page.svelte index 9fa8dfa..58940ea 100644 --- a/code/app/src/routes/(main)/(public)/sign-up/+page.svelte +++ b/code/app/src/routes/(main)/(public)/sign-up/+page.svelte @@ -1,10 +1,10 @@ <script lang="ts"> - import { goto } from "$app/navigation"; - import type { CreateAccountPayload } from "$lib/api/account"; - import { Button, Input, Alert } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; - import { FormError } from "$lib/models/internal/FormError"; - import { AccountService } from "$lib/services/account-service"; + import {goto} from "$app/navigation"; + import type {CreateAccountPayload} from "$api/account"; + import {Button, Input, Alert} from "$components"; + import LL from "$i18n/i18n-svelte"; + import {FormError} from "$models/internal/FormError"; + import {AccountService} from "$services/account-service"; const formData = { username: { @@ -76,30 +76,30 @@ <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 showErrorAlert} - <Alert title={formError.title} message={formError.subtitle} type="error" class="mb-2" /> + <Alert title={formError.title} message={formError.subtitle} type="error" class="mb-2"/> {/if} <form class="space-y-6" on:submit|preventDefault={submit_form_async}> <Input - label={$LL.emailAddress()} - id="email" - name="email" - autocomplete="email" - required - type="email" - bind:value={formData.username.value} - errors={formData.username.errors} + label={$LL.emailAddress()} + id="email" + name="email" + autocomplete="email" + required + type="email" + bind:value={formData.username.value} + errors={formData.username.errors} /> <Input - label={$LL.password()} - id="password" - name="password" - required - type="password" - bind:value={formData.password.value} - errors={formData.password.errors} + label={$LL.password()} + id="password" + name="password" + required + type="password" + bind:value={formData.password.value} + errors={formData.password.errors} /> - <Button type="submit" text={$LL.submit()} {loading} fullWidth /> + <Button type="submit" text={$LL.submit()} {loading} fullWidth/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/+layout.server.ts b/code/app/src/routes/(main)/+layout.server.ts index cd41734..086d1c0 100644 --- a/code/app/src/routes/(main)/+layout.server.ts +++ b/code/app/src/routes/(main)/+layout.server.ts @@ -1,27 +1,27 @@ -import { api_base, CookieNames } from "$lib/configuration"; -import { log_debug, log_error } from "$lib/logger"; -import { error, redirect } from "@sveltejs/kit"; -import { Temporal } from "temporal-polyfill"; -import type { LayoutServerLoad } from "./$types"; +import {api_base, CookieNames} from "$configuration"; +import {log_debug, log_error} from "$help/logger"; +import {error, redirect} from "@sveltejs/kit"; +import {Temporal} from "temporal-polyfill"; +import type {LayoutServerLoad} from "./$types"; -export const load: LayoutServerLoad = async ({ route, cookies, locals }) => { +export const load: LayoutServerLoad = async ({route, cookies, locals}) => { const isBaseRoute = route.id === "/(main)"; const isPublicRoute = (route.id?.startsWith("/(main)/(public)") || isBaseRoute) ?? true; const sessionIsValid = (await cached_result<Response>("sessionCheck", 120, () => fetch(api_base("_/valid-session"), { headers: { - Cookie: CookieNames.session + "=" + cookies.get(CookieNames.session) - } + Cookie: CookieNames.session + "=" + cookies.get(CookieNames.session), + }, }).catch((e) => { log_error(e); throw error(503, { - message: "We are experiencing a service disruption! Have patience while we resolve the issue." - }) + message: "We are experiencing a service disruption! Have patience while we resolve the issue.", + }); }))).ok; log_debug("Base Layout loaded", { sessionIsValid, isPublicRoute, - routeId: route.id + routeId: route.id, }); if (sessionIsValid && isPublicRoute) { @@ -31,17 +31,18 @@ export const load: LayoutServerLoad = async ({ route, cookies, locals }) => { } return { - locale: locals.locale - } -} + locale: locals.locale, + }; +}; let resultCache = {}; + async function cached_result<T>(key: string, staleAfterSeconds: number, code: any) { if (!resultCache[key]) { resultCache[key] = { l: 0, - c: undefined as T - } + c: undefined as T, + }; } const staleEpoch = ((resultCache[key]?.l ?? 0) + staleAfterSeconds); const isStale = staleEpoch < Temporal.Now.instant().epochSeconds; @@ -54,7 +55,7 @@ async function cached_result<T>(key: string, staleAfterSeconds: number, code: an cacheKey: key, isStale, cache: resultCache[key], - staleEpoch + staleEpoch, }); return resultCache[key].c as T; diff --git a/code/app/src/routes/(main)/+layout.svelte b/code/app/src/routes/(main)/+layout.svelte index 7d4ce05..2b96527 100644 --- a/code/app/src/routes/(main)/+layout.svelte +++ b/code/app/src/routes/(main)/+layout.svelte @@ -1,21 +1,21 @@ <script lang="ts"> import "../../app.pcss"; - import { setLocale } from "$lib/i18n/i18n-svelte"; - import { ExclamationTriangleIcon } from "$lib/components/icons"; - import type { LayoutData } from "./$types"; + import {setLocale} from "$i18n/i18n-svelte"; + import {ExclamationTriangleIcon} from "$components/icons"; + import type {LayoutData} from "./$types"; let online = true; export let data: LayoutData; setLocale(data.locale); </script> -<svelte:window bind:online /> +<svelte:window bind:online/> {#if !online} <div class="bg-yellow-50 relative z-50 p-4"> <div class="flex"> <div class="flex-shrink-0"> - <ExclamationTriangleIcon class="bg-yellow-50 text-yellow-500" /> + <ExclamationTriangleIcon class="bg-yellow-50 text-yellow-500"/> </div> <div class="ml-3"> <p class="text-sm text-yellow-700">You seem to be offline, please check your internet connection.</p> @@ -24,4 +24,4 @@ </div> {/if} -<slot /> +<slot/> diff --git a/code/app/src/routes/(main)/+layout.ts b/code/app/src/routes/(main)/+layout.ts index cf08d66..0509aaf 100644 --- a/code/app/src/routes/(main)/+layout.ts +++ b/code/app/src/routes/(main)/+layout.ts @@ -1,10 +1,10 @@ -import type { LayoutLoad } from './$types' -import type { Locales } from '$lib/i18n/i18n-types' -import { loadLocaleAsync } from '$lib/i18n/i18n-util.async' -import { setLocale } from '$lib/i18n/i18n-svelte' +import type {LayoutLoad} from "./$types"; +import type {Locales} from "$i18n/i18n-types"; +import {loadLocaleAsync} from "$i18n/i18n-util.async"; +import {setLocale} from "$i18n/i18n-svelte"; -export const load: LayoutLoad<{ locale: Locales }> = async ({ data: { locale } }) => { - await loadLocaleAsync(locale) - setLocale(locale) - return { locale } -}
\ No newline at end of file +export const load: LayoutLoad<{ locale: Locales }> = async ({data: {locale}}) => { + await loadLocaleAsync(locale); + setLocale(locale); + return {locale}; +};
\ No newline at end of file |
