summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/frontpage/package.json4
-rw-r--r--apps/frontpage/pnpm-lock.yaml122
-rw-r--r--apps/frontpage/src/app.html2
-rw-r--r--apps/frontpage/src/hooks.ts7
-rw-r--r--apps/frontpage/src/routes/__layout-docs.svelte7
-rw-r--r--apps/frontpage/src/routes/__layout.svelte5
-rw-r--r--apps/frontpage/src/routes/app.scss1
-rw-r--r--apps/frontpage/static/pre.css128
-rw-r--r--apps/frontpage/svelte.config.js2
-rw-r--r--apps/portal/src/app/components/user-menu.svelte70
-rw-r--r--apps/portal/src/app/index.scss4
-rw-r--r--apps/portal/src/app/index.svelte35
-rw-r--r--apps/portal/src/app/pages/_layout.svelte3
-rw-r--r--apps/portal/src/app/pages/_layout@loggedin.svelte69
-rw-r--r--apps/portal/src/app/pages/home.svelte72
-rw-r--r--apps/portal/src/app/pages/login.svelte8
-rw-r--r--apps/portal/src/app/pages/profile/index.svelte16
-rw-r--r--apps/portal/src/package.json2
-rw-r--r--apps/portal/src/pnpm-lock.yaml15
-rw-r--r--apps/portal/src/tsconfig.json3
-rw-r--r--apps/projects/src/app/index.svelte3
-rw-r--r--apps/projects/src/app/lib/services/user-service.ts21
-rw-r--r--apps/web-shared/src/components/breadcrumb/bread.svelte9
-rw-r--r--apps/web-shared/src/components/breadcrumb/crumb.svelte22
-rw-r--r--apps/web-shared/src/components/breadcrumb/index.ts7
-rw-r--r--apps/web-shared/src/components/link-card.svelte47
-rw-r--r--apps/web-shared/src/components/menu/menu.svelte2
-rw-r--r--apps/web-shared/src/components/theme-switcher.svelte71
-rw-r--r--apps/web-shared/src/components/user-menu.svelte99
-rw-r--r--apps/web-shared/src/lib/helpers.ts4
-rw-r--r--apps/web-shared/src/lib/session.ts8
-rw-r--r--apps/web-shared/src/styles/components/auto-sized-grid.scss56
-rw-r--r--apps/web-shared/src/styles/components/breadcrumbs.scss18
-rw-r--r--apps/web-shared/src/styles/components/link-card.scss56
-rw-r--r--apps/web-shared/src/styles/components/user-menu.scss8
35 files changed, 948 insertions, 58 deletions
diff --git a/apps/frontpage/package.json b/apps/frontpage/package.json
index cde11de..6943b38 100644
--- a/apps/frontpage/package.json
+++ b/apps/frontpage/package.json
@@ -15,7 +15,9 @@
"@sveltejs/adapter-static": "1.0.0-next.34",
"@sveltejs/kit": "next",
"sass": "^1.52.2",
- "svelte": "^3.44.0"
+ "svelte": "^3.44.0",
+ "svelte-preprocess": "^4.10.7",
+ "typescript": "^4.7.3"
},
"type": "module"
} \ No newline at end of file
diff --git a/apps/frontpage/pnpm-lock.yaml b/apps/frontpage/pnpm-lock.yaml
index 5b1b562..a80546e 100644
--- a/apps/frontpage/pnpm-lock.yaml
+++ b/apps/frontpage/pnpm-lock.yaml
@@ -7,6 +7,8 @@ specifiers:
'@sveltejs/kit': next
sass: ^1.52.2
svelte: ^3.44.0
+ svelte-preprocess: ^4.10.7
+ typescript: ^4.7.3
devDependencies:
'@playwright/test': 1.22.2
@@ -15,6 +17,8 @@ devDependencies:
'@sveltejs/kit': 1.0.0-next.348_sass@1.52.2+svelte@3.48.0
sass: 1.52.2
svelte: 3.48.0
+ svelte-preprocess: 4.10.7_qkexsrbgfwouorfsttztq6wl2m
+ typescript: 4.7.3
packages:
@@ -147,6 +151,16 @@ packages:
resolution: {integrity: sha512-UXdBxNGqTMtm7hCwh9HtncFVLrXoqA3oJW30j6XWp5BH/wu3mVeaxo7cq5benFdBw34HB3XDT2TRPI7rXZ+mDg==}
dev: true
+ /@types/pug/2.0.6:
+ resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
+ dev: true
+
+ /@types/sass/1.43.1:
+ resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
+ dependencies:
+ '@types/node': 17.0.40
+ dev: true
+
/@vercel/nft/0.19.1:
resolution: {integrity: sha512-klR5oN7S3WJsZz0r6Xsq7o8YlFEyU3/00VmlpZzIPVFzKfbcEjXo/sVR5lQBUqNKuOzhcbxaFtzW9aOyHjmPYA==}
hasBin: true
@@ -256,6 +270,10 @@ packages:
fill-range: 7.0.1
dev: true
+ /buffer-crc32/0.2.13:
+ resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
+ dev: true
+
/chokidar/3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@@ -339,6 +357,11 @@ packages:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: true
+ /detect-indent/6.1.0:
+ resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
+ engines: {node: '>=8'}
+ dev: true
+
/detect-libc/1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
@@ -354,6 +377,10 @@ packages:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
+ /es6-promise/3.3.1:
+ resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
+ dev: true
+
/esbuild-android-64/0.14.42:
resolution: {integrity: sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A==}
engines: {node: '>=12'}
@@ -779,6 +806,12 @@ packages:
yallist: 4.0.0
dev: true
+ /magic-string/0.25.9:
+ resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
+ dependencies:
+ sourcemap-codec: 1.4.8
+ dev: true
+
/magic-string/0.26.2:
resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==}
engines: {node: '>=12'}
@@ -801,6 +834,11 @@ packages:
picomatch: 2.3.1
dev: true
+ /min-indent/1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+ dev: true
+
/minimatch/3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
@@ -1155,6 +1193,15 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true
+ /sander/0.5.1:
+ resolution: {integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0=}
+ dependencies:
+ es6-promise: 3.3.1
+ graceful-fs: 4.2.10
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+ dev: true
+
/sass/1.52.2:
resolution: {integrity: sha512-mfHB2VSeFS7sZlPv9YohB9GB7yWIgQNTGniQwfQ04EoQN0wsQEv7SwpCwy/x48Af+Z3vDeFXz+iuXM3HK/phZQ==}
engines: {node: '>=12.0.0'}
@@ -1199,6 +1246,16 @@ packages:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: true
+ /sorcery/0.10.0:
+ resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
+ hasBin: true
+ dependencies:
+ buffer-crc32: 0.2.13
+ minimist: 1.2.6
+ sander: 0.5.1
+ sourcemap-codec: 1.4.8
+ dev: true
+
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
@@ -1252,6 +1309,13 @@ packages:
ansi-regex: 5.0.1
dev: true
+ /strip-indent/3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ min-indent: 1.0.1
+ dev: true
+
/strip-json-comments/2.0.1:
resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=}
engines: {node: '>=0.10.0'}
@@ -1271,6 +1335,58 @@ packages:
svelte: 3.48.0
dev: true
+ /svelte-preprocess/4.10.7_qkexsrbgfwouorfsttztq6wl2m:
+ resolution: {integrity: sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==}
+ engines: {node: '>= 9.11.2'}
+ requiresBuild: true
+ peerDependencies:
+ '@babel/core': ^7.10.2
+ coffeescript: ^2.5.1
+ less: ^3.11.3 || ^4.0.0
+ node-sass: '*'
+ postcss: ^7 || ^8
+ postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0
+ pug: ^3.0.0
+ sass: ^1.26.8
+ stylus: ^0.55.0
+ sugarss: ^2.0.0
+ svelte: ^3.23.0
+ typescript: ^3.9.5 || ^4.0.0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ coffeescript:
+ optional: true
+ less:
+ optional: true
+ node-sass:
+ optional: true
+ postcss:
+ optional: true
+ postcss-load-config:
+ optional: true
+ pug:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ '@types/pug': 2.0.6
+ '@types/sass': 1.43.1
+ detect-indent: 6.1.0
+ magic-string: 0.25.9
+ sass: 1.52.2
+ sorcery: 0.10.0
+ strip-indent: 3.0.0
+ svelte: 3.48.0
+ typescript: 4.7.3
+ dev: true
+
/svelte/3.48.0:
resolution: {integrity: sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==}
engines: {node: '>= 8'}
@@ -1319,6 +1435,12 @@ packages:
resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=}
dev: true
+ /typescript/4.7.3:
+ resolution: {integrity: sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==}
+ engines: {node: '>=4.2.0'}
+ hasBin: true
+ dev: true
+
/util-deprecate/1.0.2:
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
dev: true
diff --git a/apps/frontpage/src/app.html b/apps/frontpage/src/app.html
index 5893110..e46fbe3 100644
--- a/apps/frontpage/src/app.html
+++ b/apps/frontpage/src/app.html
@@ -4,6 +4,8 @@
<meta charset="utf-8"/>
<link rel="icon"
href="%sveltekit.assets%/favicon.png"/>
+ <link rel="stylesheet"
+ href="%sveltekit.assets%/pre.css"/>
<script>
const value = `; ${document.cookie}`;
const parts = value.split(`; go_theme=`);
diff --git a/apps/frontpage/src/hooks.ts b/apps/frontpage/src/hooks.ts
new file mode 100644
index 0000000..ca80d40
--- /dev/null
+++ b/apps/frontpage/src/hooks.ts
@@ -0,0 +1,7 @@
+/** @type {import("@sveltejs/kit").Handle} */
+export async function handle({event, resolve}) {
+ const response = await resolve(event, {
+ ssr: false
+ });
+ return response;
+} \ No newline at end of file
diff --git a/apps/frontpage/src/routes/__layout-docs.svelte b/apps/frontpage/src/routes/__layout-docs.svelte
index 6f06f52..d0a17ef 100644
--- a/apps/frontpage/src/routes/__layout-docs.svelte
+++ b/apps/frontpage/src/routes/__layout-docs.svelte
@@ -1,6 +1,13 @@
+<script>
+ import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
+</script>
<main class="padding-md">
<nav>
<a href="/">Go back</a>
</nav>
<slot></slot>
+ <div class="position-fixed right-0 top-0 margin-md z-index-2">
+ <ThemeSwitcher/>
+ </div>
+
</main>
diff --git a/apps/frontpage/src/routes/__layout.svelte b/apps/frontpage/src/routes/__layout.svelte
index f1a7eaa..ea37fe7 100644
--- a/apps/frontpage/src/routes/__layout.svelte
+++ b/apps/frontpage/src/routes/__layout.svelte
@@ -1,5 +1,6 @@
<script>
import {portal_base} from "$shared/lib/configuration";
+ import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
import "./app.scss";
import {afterNavigate} from "$app/navigation";
import {page} from "$app/stores";
@@ -77,4 +78,8 @@
<main class="position-relative padding-sm z-index-1 flex-grow">
<slot></slot>
</main>
+
+ <div class="position-fixed right-0 top-0 margin-md z-index-2">
+ <ThemeSwitcher/>
+ </div>
</div>
diff --git a/apps/frontpage/src/routes/app.scss b/apps/frontpage/src/routes/app.scss
index 73a46ba..6ba6e97 100644
--- a/apps/frontpage/src/routes/app.scss
+++ b/apps/frontpage/src/routes/app.scss
@@ -5,3 +5,4 @@
@use '../../web-shared/src/styles/custom-style/typography';
@use '../../web-shared/src/styles/custom-style/util';
@use '../../web-shared/src/styles/components/responsive-sidebar';
+@use '../../web-shared/src/styles/components/light-dark-switch';
diff --git a/apps/frontpage/static/pre.css b/apps/frontpage/static/pre.css
new file mode 100644
index 0000000..9c9446e
--- /dev/null
+++ b/apps/frontpage/static/pre.css
@@ -0,0 +1,128 @@
+:root {
+ --loader-primary: hsl(250, 84%, 54%);
+ --loader-accent: hsl(342, 89%, 48%);
+ --loader-contrast: hsl(180, 1%, 84%);
+ --loader-easing: cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+
+[data-theme="dark"] :root {
+ --loader-primary: hsl(250, 93%, 65%);
+ --loader-accent: hsl(342, 92%, 54%);
+ --loader-contrast: hsl(208, 12%, 24%);
+ --loader-easing: cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+
+[data-theme="dark"] {
+ background-color: hsl(203, 24%, 13%);
+}
+
+.fill-loader {
+ position: relative;
+ overflow: hidden;
+ display: inline-block;
+ margin: 3rem;
+}
+
+.fill-loader__fill {
+ position: absolute;
+}
+
+@supports (-webkit-animation-name: this) or (animation-name: this) {
+ .fill-loader__label {
+ position: absolute;
+ clip: rect(1px, 1px, 1px, 1px);
+ -webkit-clip-path: inset(50%);
+ clip-path: inset(50%);
+ }
+}
+
+@supports (-webkit-animation-name: this) or (animation-name: this) {
+ .fill-loader--v4 {
+ width: 90%;
+ max-width: 300px;
+ }
+
+ .fill-loader--v4 .fill-loader__base {
+ height: 4px;
+ background-color: var(--loader-contrast);
+ }
+
+ .fill-loader--v4 .fill-loader__fill {
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 100%;
+ background-color: var(--loader-primary);
+ -webkit-animation: fill-loader-4 1.6s infinite var(--loader-easing);
+ animation: fill-loader-4 1.6s infinite var(--loader-easing);
+ will-change: left, right;
+ }
+}
+
+@-webkit-keyframes fill-loader-4 {
+ 0% {
+ left: 0;
+ right: 100%;
+ background-color: var(--loader-primary);
+ }
+
+ 10%,
+ 60% {
+ left: 0;
+ }
+
+ 40%,
+ 90% {
+ right: 0;
+ }
+
+ 50% {
+ left: 100%;
+ background-color: var(--loader-primary);
+ }
+
+ 51% {
+ left: 0;
+ right: 100%;
+ background-color: var(--loader-accent);
+ }
+
+ 100% {
+ left: 100%;
+ background-color: var(--loader-accent);
+ }
+}
+
+@keyframes fill-loader-4 {
+ 0% {
+ left: 0;
+ right: 100%;
+ background-color: var(--loader-primary);
+ }
+
+ 10%,
+ 60% {
+ left: 0;
+ }
+
+ 40%,
+ 90% {
+ right: 0;
+ }
+
+ 50% {
+ left: 100%;
+ background-color: var(--loader-primary);
+ }
+
+ 51% {
+ left: 0;
+ right: 100%;
+ background-color: var(--loader-accent);
+ }
+
+ 100% {
+ left: 100%;
+ background-color: var(--loader-accent);
+ }
+}
diff --git a/apps/frontpage/svelte.config.js b/apps/frontpage/svelte.config.js
index 60a9bcf..9646d6b 100644
--- a/apps/frontpage/svelte.config.js
+++ b/apps/frontpage/svelte.config.js
@@ -1,7 +1,9 @@
import adapter from "@sveltejs/adapter-static";
+import preprocess from "svelte-preprocess"
/** @type {import("@sveltejs/kit").Config} */
const config = {
+ preprocess: preprocess(),
kit: {
prerender: {
default: true,
diff --git a/apps/portal/src/app/components/user-menu.svelte b/apps/portal/src/app/components/user-menu.svelte
new file mode 100644
index 0000000..b0cfc8a
--- /dev/null
+++ b/apps/portal/src/app/components/user-menu.svelte
@@ -0,0 +1,70 @@
+<script>
+ import {end_session} from "$shared/lib/session";
+ import {onMount} from "svelte";
+ import {Menu, MenuItem, MenuItemSeparator} from "$shared/components/menu";
+ import {replace} from "svelte-spa-router";
+
+ let userMenuTrigger;
+ let showUserMenu = false;
+
+ export let avatar = "";
+ export let name;
+ export let secondary = "";
+ let userMenuId;
+
+ async function on_logout() {
+ await end_session(() => {
+ replace("/login");
+ });
+ }
+
+ onMount(() => {
+ userMenuTrigger = document.getElementById("open-user-menu");
+ });
+</script>
+
+<button class="reset user-menu-control"
+ id="open-user-menu"
+ aria-controls="{userMenuId}"
+ on:click={() => showUserMenu = true}>
+ {#if avatar}
+ <figure class="user-menu-control__img-wrapper radius-50%">
+ <img class="user-menu-control__img"
+ src="{avatar}"
+ alt="Avatar">
+ </figure>
+ {/if}
+
+ <div class="margin-x-xs user-menu__meta">
+ <p class="user-menu__meta-title text-sm line-height-1 padding-y-xxxxs font-semibold color-contrast-higher text-truncate">{name}</p>
+ {#if secondary}
+ <p class="text-xs color-contrast-medium line-height-1 padding-bottom-xxxxs">{secondary}</p>
+ {/if}
+ </div>
+
+ <svg class="icon icon--xxs"
+ aria-hidden="true"
+ viewBox="0 0 12 12">
+ <polyline points="1 4 6 9 11 4"
+ fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"/>
+ </svg>
+</button>
+
+<Menu trigger={userMenuTrigger}
+ bind:id={userMenuId}
+ bind:show="{showUserMenu}">
+ <div slot="options">
+ <MenuItem on:click={() => replace("/profile")}>
+ <span>Profile</span>
+ </MenuItem>
+ <MenuItemSeparator/>
+ <MenuItem danger="true"
+ on:click={() => on_logout()}>
+ Logout
+ </MenuItem>
+ </div>
+</Menu>
diff --git a/apps/portal/src/app/index.scss b/apps/portal/src/app/index.scss
index dd4ddb6..8633a7d 100644
--- a/apps/portal/src/app/index.scss
+++ b/apps/portal/src/app/index.scss
@@ -20,3 +20,7 @@
@use '../../web-shared/src/styles/components/alert';
@use '../../web-shared/src/styles/components/details';
@use '../../web-shared/src/styles/components/light-dark-switch';
+@use '../../web-shared/src/styles/components/link-card';
+@use '../../web-shared/src/styles/components/auto-sized-grid';
+@use '../../web-shared/src/styles/components/menu';
+@use '../../web-shared/src/styles/components/user-menu';
diff --git a/apps/portal/src/app/index.svelte b/apps/portal/src/app/index.svelte
index 0082aa2..fe143bc 100644
--- a/apps/portal/src/app/index.svelte
+++ b/apps/portal/src/app/index.svelte
@@ -2,32 +2,54 @@
<svelte:window bind:online={online}/>
<script>
- import {projects_base} from "$shared/lib/configuration";
- import Router from "svelte-spa-router";
+ import Router, {replace} from "svelte-spa-router";
import {wrap} from "svelte-spa-router/wrap";
import {is_active} from "$shared/lib/session";
import SignUp from "$app/pages/sign-up.svelte";
import Login from "$app/pages/login.svelte";
import Forgot from "$app/pages/forgot.svelte";
import Reset from "$app/pages/reset-password.svelte";
+ import Home from "$app/pages/home.svelte";
+ import ProfileHome from "$app/pages/profile/index.svelte";
import PreHeader from "$shared/components/pre-header.svelte";
let online = true;
- async function user_is_logged_in() {
- if (await is_active()) {
- location.replace(projects_base("#/home"));
+ const publicRoutes = ["/login", "/signup", "/reset-password", "/forgot"];
+ const guardedRoutes = ["/", "/home", "/profile"];
+
+ async function user_is_logged_in(event) {
+ const isActive = await is_active();
+ if (!isActive && !publicRoutes.includes(event.route)) {
+ return false;
+ }
+ if (isActive && !guardedRoutes.includes(event.route)) {
+ await replace("/");
}
return true;
}
+ function route_guarded(event) {
+ if (!publicRoutes.includes(event.detail.route)) {
+ replace("/login");
+ }
+ }
+
const routes = {
"/login": wrap({
component: Login,
conditions: [user_is_logged_in],
}),
+ "/home": wrap({
+ component: Home,
+ conditions: [user_is_logged_in],
+ }),
+ "/profile": wrap({
+ component: ProfileHome,
+ conditions: [user_is_logged_in],
+ }),
"/": wrap({
- component: Login,
+ component: Home,
conditions: [user_is_logged_in],
}),
"/signup": wrap({
@@ -50,6 +72,7 @@
<Router
{routes}
restoreScrollState={true}
+ on:conditionsFailed={route_guarded}
on:routeLoading={() => {
document.getElementById("loader").style.display = "inline-block";
}}
diff --git a/apps/portal/src/app/pages/_layout.svelte b/apps/portal/src/app/pages/_layout.svelte
index d5af444..04cbdd3 100644
--- a/apps/portal/src/app/pages/_layout.svelte
+++ b/apps/portal/src/app/pages/_layout.svelte
@@ -1,5 +1,6 @@
<script>
import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
+ import {frontpage_base} from "$shared/lib/configuration";
</script>
<style>
@@ -26,7 +27,7 @@
height: auto;
}
</style>
-
+
<main class="container-fluid padding-x-xs padding-x-xxl@xs padding-y-md padding-y-lg@md max-width-sm">
<div class="z-index-2 position-relative">
<slot/>
diff --git a/apps/portal/src/app/pages/_layout@loggedin.svelte b/apps/portal/src/app/pages/_layout@loggedin.svelte
new file mode 100644
index 0000000..72515df
--- /dev/null
+++ b/apps/portal/src/app/pages/_layout@loggedin.svelte
@@ -0,0 +1,69 @@
+<script>
+ import ThemeSwitcher from "$shared/components/theme-switcher.svelte";
+ import UserMenu from "$app/components/user-menu.svelte";
+ import {get_session_data} from "$shared/lib/session";
+
+ const session = get_session_data();
+</script>
+
+<style>
+ #decoration {
+ position: absolute;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ z-index: 1;
+ }
+
+ #decoration svg {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+ width: 134%;
+ min-width: 1280px;
+ max-width: 1920px;
+ height: auto;
+ }
+</style>
+
+<main class="container-fluid padding-x-xs padding-x-xxl@xs padding-y-md padding-y-lg@md">
+ <div class="z-index-2 position-relative">
+ <slot/>
+ </div>
+
+ <div class="flex flex-row gap-xs position-fixed right-0 top-0 margin-md z-index-2">
+ <UserMenu name="{session.profile?.username}"/>
+ <ThemeSwitcher/>
+ </div>
+
+ <figure id="decoration"
+ aria-hidden="true">
+ <svg class="color-contrast-higher opacity-10%"
+ viewBox="0 0 1920 450"
+ fill="none">
+ <g stroke="currentColor"
+ stroke-width="2"
+ stroke-linejoin="round"
+ stroke-linecap="round">
+ <path d="M1449 94.9993V3L1354 48.9995L1259 3V94.9993L1354 140.999L1449 94.9993Z"/>
+ <path d="M1639 94.9993V3L1544 48.9995L1449 3V94.9993L1544 140.999L1639 94.9993Z"/>
+ <path d="M1354 49.0002V141"/>
+ <path d="M1544 49.0002V141"/>
+ <path d="M1449 94.9995L1544 140.999L1449 186.999L1354 140.999L1449 94.9995Z"/>
+ <path d="M1544 141V232.999L1449 278.999L1354 232.999V141"/>
+ <path d="M1449 187V279"/>
+ <path d="M1544 264L1639 310L1544 355.999L1449 310L1544 264Z"/>
+ <path d="M1639 310V402L1544 447.999L1449 402V310"/>
+ <path d="M1544 356.001V448"/>
+ <path d="M1639 94.9995L1734 140.999L1639 186.999L1544 140.999L1639 94.9995Z"/>
+ <path d="M1734 141V232.999L1639 278.999L1544 232.999V141"/>
+ <path d="M1639 187V279"/>
+ </g>
+ </svg>
+ </figure>
+</main>
diff --git a/apps/portal/src/app/pages/home.svelte b/apps/portal/src/app/pages/home.svelte
new file mode 100644
index 0000000..b9b9829
--- /dev/null
+++ b/apps/portal/src/app/pages/home.svelte
@@ -0,0 +1,72 @@
+<script>
+ import {projects_base} from "$shared/lib/configuration";
+ import {get_session_data} from "$shared/lib/session";
+ import {push} from "svelte-spa-router";
+ import Layout from "./_layout@loggedin.svelte";
+ import LinkCard from "$shared/components/link-card.svelte";
+ import {UserIcon, UsersIcon, WatchIcon} from "svelte-feather-icons";
+
+ let showUsers = true;
+ const session = get_session_data();
+</script>
+
+<Layout>
+ <div class="grid gap-md">
+ <div class="row">
+ <h1 class="margin-bottom-xs">Hello {session.profile?.username}</h1>
+ <p>This is your portal to Greatoffice, here you will find all your great apps and management options.</p>
+ </div>
+ <div class="row">
+ <h2 class="margin-bottom-xs">Apps</h2>
+ <div class="grid-auto-md gap-sm">
+ <LinkCard name="Projects"
+ description="The home for your projects"
+ text="Open"
+ target="_blank"
+ title="Open Projects in a new tab"
+ href="{projects_base()}">
+ <figure slot="icon">
+ <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2">
+ <WatchIcon size="42"
+ class="color-primary"
+ strokeWidth="1.2"/>
+ </div>
+ </figure>
+ </LinkCard>
+ </div>
+ </div>
+ <div class="row">
+ <h2 class="margin-bottom-xs">Manage</h2>
+ <div class="grid-auto-md gap-sm">
+ <LinkCard name="Profile"
+ description="Manage your profile information"
+ text="Open"
+ title="Go to your profile management page"
+ on:click={() => push("/profile")}>
+ <figure slot="icon">
+ <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2">
+ <UserIcon size="42"
+ class="color-primary"
+ strokeWidth="1.2"/>
+ </div>
+ </figure>
+ </LinkCard>
+ {#if showUsers}
+ <LinkCard name="Users"
+ description="Manage your users"
+ title="Go to your users management page"
+ text="Open"
+ href="{projects_base()}">
+ <figure slot="icon">
+ <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2">
+ <UsersIcon size="42"
+ class="color-primary"
+ strokeWidth="1.2"/>
+ </div>
+ </figure>
+ </LinkCard>
+ {/if}
+ </div>
+ </div>
+ </div>
+</Layout>
diff --git a/apps/portal/src/app/pages/login.svelte b/apps/portal/src/app/pages/login.svelte
index 2822be0..db010d2 100644
--- a/apps/portal/src/app/pages/login.svelte
+++ b/apps/portal/src/app/pages/login.svelte
@@ -1,7 +1,7 @@
<script>
import {onMount} from "svelte";
- import {link, querystring} from "svelte-spa-router";
- import {api_base, projects_base, IconNames} from "$shared/lib/configuration";
+ import {link, replace, querystring} from "svelte-spa-router";
+ import {api_base, projects_base, IconNames, frontpage_base} from "$shared/lib/configuration";
import Button from "$shared/components/button.svelte";
import Alert from "$shared/components/alert.svelte";
import Tile from "$shared/components/tile.svelte";
@@ -47,7 +47,7 @@
try {
const response = await login(loginForm.values);
if (response.ok) {
- location.replace(projects_base("#/home"));
+ await replace("#/home");
} else {
if (response.data.title || response.data.text) {
loginForm.alert.show("error", {
@@ -62,7 +62,6 @@
}
}
} catch (e) {
- console.error(e);
loginForm.alert.show("error", {
title: "An error occured",
text: "Could not connect to server, please check your internet connection",
@@ -93,6 +92,7 @@
</script>
<Layout>
+ <a href="{frontpage_base()}" class="block margin-bottom-xs">Go to {frontpage_base()}</a>
<Tile>
<form on:submit|preventDefault={loginForm.submit_form}
class="max-width-xxs">
diff --git a/apps/portal/src/app/pages/profile/index.svelte b/apps/portal/src/app/pages/profile/index.svelte
new file mode 100644
index 0000000..0929c3c
--- /dev/null
+++ b/apps/portal/src/app/pages/profile/index.svelte
@@ -0,0 +1,16 @@
+<script>
+ import {push} from "svelte-spa-router";
+ import {Bread, Crumb} from "$shared/components/breadcrumb/index";
+ import Layout from "$app/pages/_layout@loggedin.svelte";
+</script>
+
+<Layout>
+ <Bread>
+ <Crumb name="Home"
+ withArrow="true"
+ isLink="true"
+ on:click={() => push("/")}/>
+ <Crumb name="Profile"/>
+ </Bread>
+ <h1>Profile</h1>
+</Layout>
diff --git a/apps/portal/src/package.json b/apps/portal/src/package.json
index e969c4e..64af86e 100644
--- a/apps/portal/src/package.json
+++ b/apps/portal/src/package.json
@@ -8,8 +8,10 @@
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "1.0.0-next.43",
+ "install": "^0.13.0",
"sass": "^1.51.0",
"svelte": "^3.48.0",
+ "svelte-feather-icons": "^4.0.0",
"svelte-preprocess": "^4.10.6",
"svelte-spa-router": "^3.2.0",
"typescript": "4.6.4",
diff --git a/apps/portal/src/pnpm-lock.yaml b/apps/portal/src/pnpm-lock.yaml
index 3b56115..0c6d187 100644
--- a/apps/portal/src/pnpm-lock.yaml
+++ b/apps/portal/src/pnpm-lock.yaml
@@ -4,8 +4,10 @@ specifiers:
'@js-temporal/polyfill': ^0.4.1
'@sveltejs/vite-plugin-svelte': 1.0.0-next.43
fuzzysort: ^1.9.0
+ install: ^0.13.0
sass: ^1.51.0
svelte: ^3.48.0
+ svelte-feather-icons: ^4.0.0
svelte-preprocess: ^4.10.6
svelte-spa-router: ^3.2.0
typescript: 4.6.4
@@ -17,8 +19,10 @@ dependencies:
devDependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.43_svelte@3.48.0+vite@2.9.8
+ install: 0.13.0
sass: 1.51.0
svelte: 3.48.0
+ svelte-feather-icons: 4.0.0
svelte-preprocess: 4.10.6_24ezlekk4ocevlsjgs2qnqmjum
svelte-spa-router: 3.2.0
typescript: 4.6.4
@@ -442,6 +446,11 @@ packages:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
+ /install/0.13.0:
+ resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==}
+ engines: {node: '>= 0.10'}
+ dev: true
+
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -650,6 +659,12 @@ packages:
engines: {node: '>= 0.4'}
dev: true
+ /svelte-feather-icons/4.0.0:
+ resolution: {integrity: sha512-4ieUsjp+VYa1r6y80jDt9zRiRUZyJNbESpRdHdJJhiBubyuXX96A7f1UZSK4olxzP6Qsg5ZAuyZlnmvD+/swAA==}
+ dependencies:
+ svelte: 3.48.0
+ dev: true
+
/svelte-hmr/0.14.11_svelte@3.48.0:
resolution: {integrity: sha512-R9CVfX6DXxW1Kn45Jtmx+yUe+sPhrbYSUp7TkzbW0jI5fVPn6lsNG9NEs5dFg5qRhFNAoVdRw5qQDLALNKhwbQ==}
engines: {node: ^12.20 || ^14.13.1 || >= 16}
diff --git a/apps/portal/src/tsconfig.json b/apps/portal/src/tsconfig.json
index e00d638..c60fce6 100644
--- a/apps/portal/src/tsconfig.json
+++ b/apps/portal/src/tsconfig.json
@@ -17,9 +17,6 @@
"checkJs": false,
"paths": {
"$app/*": [
- "./_public/*"
- ],
- "$app/*": [
"./app/*"
],
"$shared/*": [
diff --git a/apps/projects/src/app/index.svelte b/apps/projects/src/app/index.svelte
index c217797..77d290d 100644
--- a/apps/projects/src/app/index.svelte
+++ b/apps/projects/src/app/index.svelte
@@ -24,7 +24,6 @@
}
const queryClient = new QueryClient();
-
const routes = {
"/home": wrap({
@@ -47,7 +46,9 @@
"*": NotFound,
};
</script>
+
<PreHeader show="{!online}">You seem to be offline, please check your internet connection.</PreHeader>
+
<QueryClientProvider client={queryClient}>
<Router
{routes}
diff --git a/apps/projects/src/app/lib/services/user-service.ts b/apps/projects/src/app/lib/services/user-service.ts
index 6e4a200..4155819 100644
--- a/apps/projects/src/app/lib/services/user-service.ts
+++ b/apps/projects/src/app/lib/services/user-service.ts
@@ -1,21 +1,14 @@
-import {delete_account, logout} from "$shared/lib/api/user";
import {portal_base} from "$shared/lib/configuration";
-import {clear_session_data} from "$shared/lib/session";
+import {end_session} from "$shared/lib/session";
import {clear_categories} from "$app/lib/stores/categories";
import {clear_entries} from "$app/lib/stores/entries";
import {clear_labels} from "$app/lib/stores/labels";
export async function logout_user(reason: string = "") {
- await logout();
- clear_session_data();
- clear_categories();
- clear_labels();
- clear_entries();
- location.replace(portal_base("#/login" + (reason ? "?" + reason : "")));
-}
-
-export async function delete_user() {
- await delete_account();
- clear_session_data();
- location.replace(portal_base("#/login?deleted"));
+ await end_session(() => {
+ clear_categories();
+ clear_labels();
+ clear_entries();
+ location.replace(portal_base("#/login" + (reason ? "?" + reason : "")));
+ });
}
diff --git a/apps/web-shared/src/components/breadcrumb/bread.svelte b/apps/web-shared/src/components/breadcrumb/bread.svelte
new file mode 100644
index 0000000..4cf0698
--- /dev/null
+++ b/apps/web-shared/src/components/breadcrumb/bread.svelte
@@ -0,0 +1,9 @@
+<script lang="ts">
+ export type sizes = "big"|"small";
+ export let size: sizes = "small";
+</script>
+<nav class="breadcrumbs {size === 'small' ? 'text-sm' : 'text-lg'}">
+ <ol class="flex flex-wrap gap-xxs">
+ <slot></slot>
+ </ol>
+</nav>
diff --git a/apps/web-shared/src/components/breadcrumb/crumb.svelte b/apps/web-shared/src/components/breadcrumb/crumb.svelte
new file mode 100644
index 0000000..7621de6
--- /dev/null
+++ b/apps/web-shared/src/components/breadcrumb/crumb.svelte
@@ -0,0 +1,22 @@
+<script>
+ export let name;
+ export let withArrow = false;
+ export let isLink = false;
+</script>
+<li class="breadcrumbs__item">
+ <span class="color-inherit {isLink ? 'cursor-pointer color-primary-light' : ''}"
+ on:click>{name}</span>
+ {#if withArrow}
+ <svg class="icon margin-left-xxxs color-contrast-low"
+ aria-hidden="true"
+ viewBox="0 0 16 16">
+ <polyline fill="none"
+ stroke="currentColor"
+ stroke-width="1"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-miterlimit="10"
+ points="6.5,3.5 11,8 6.5,12.5 "></polyline>
+ </svg>
+ {/if}
+</li>
diff --git a/apps/web-shared/src/components/breadcrumb/index.ts b/apps/web-shared/src/components/breadcrumb/index.ts
new file mode 100644
index 0000000..485ed7b
--- /dev/null
+++ b/apps/web-shared/src/components/breadcrumb/index.ts
@@ -0,0 +1,7 @@
+import Bread from "./bread.svelte";
+import Crumb from "./crumb.svelte";
+
+export {
+ Bread,
+ Crumb
+};
diff --git a/apps/web-shared/src/components/link-card.svelte b/apps/web-shared/src/components/link-card.svelte
new file mode 100644
index 0000000..0c15a53
--- /dev/null
+++ b/apps/web-shared/src/components/link-card.svelte
@@ -0,0 +1,47 @@
+<script>
+ export let text = "View";
+ export let name;
+ export let description = null;
+ export let href = null;
+ export let target;
+ export let title = null;
+</script>
+
+<a class="link-card flex flex-column bg-light cursor-pointer radius-md {$$restProps.class??''}"
+ {href}
+ {target}
+ {title}
+ on:click
+ aria-label="Link label">
+ <div class="padding-md">
+ <div class="flex flex-wrap gap-xs items-center">
+ <slot name="icon"></slot>
+ <div class="line-height-xs">
+ <p class="text-lg font-semibold color-contrast-higher">{name}</p>
+ {#if description}
+ <p class="color-contrast-low margin-top-xxxs">{description}</p>
+ {/if}
+ </div>
+ </div>
+ </div>
+ <div class="link-card__footer margin-top-auto border-top border-contrast-lower">
+ <p class="text-sm">{text}</p>
+ <div>
+ <svg class="icon icon--sm"
+ viewBox="0 0 24 24">
+ <g fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2">
+ <line x1="3"
+ y1="12"
+ x2="21"
+ y2="12"/>
+ <polyline points="15 6 21 12 15 18"/>
+ </g>
+ </svg>
+ </div>
+ </div>
+</a>
+
diff --git a/apps/web-shared/src/components/menu/menu.svelte b/apps/web-shared/src/components/menu/menu.svelte
index 33b1160..33517ab 100644
--- a/apps/web-shared/src/components/menu/menu.svelte
+++ b/apps/web-shared/src/components/menu/menu.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
import {random_string} from "$shared/lib/helpers";
- export let id = "__menu_" + random_string(3);
+ export const id = "__menu_" + random_string(3);
export let trigger: HTMLElement;
export let show = false;
diff --git a/apps/web-shared/src/components/theme-switcher.svelte b/apps/web-shared/src/components/theme-switcher.svelte
index 26ae507..d0a43b3 100644
--- a/apps/web-shared/src/components/theme-switcher.svelte
+++ b/apps/web-shared/src/components/theme-switcher.svelte
@@ -42,7 +42,8 @@
}
</script>
-<div class="ld-switch" data-theme-switcher-element>
+<div class="ld-switch"
+ data-theme-switcher-element>
<button class="reset ld-switch-btn"
on:click={() => show =!show}>
<span class="sr-only">{selection}</span>
@@ -64,39 +65,46 @@
</svg>
{:else if selection === "light"}
<svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20">
- <title>light-auto</title>
+ viewBox="0 0 20 20"><title>light</title>
<g fill="currentColor">
- <path d="M10 14a4 4 0 1 1 3.465-6"
- fill-opacity=".2"
+ <circle cx="10"
+ cy="10"
+ r="4"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></circle>
+ <path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
- stroke-width="2"></path>
+ stroke-width="2"
+ d="M10 1v1.5"></path>
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
- d="M12 18l2.5-7h1l2.5 7"></path>
+ d="M16.364 3.636l-1.061 1.061"></path>
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
- d="M12.714 16h4.572"></path>
+ d="M19 10h-1.5"></path>
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
- d="M10 1v1.5"></path>
+ d="M16.364 16.364l-1.061-1.061"></path>
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
- d="M16.364 3.636l-1.061 1.061"></path>
+ d="M10 19v-1.5"></path>
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
@@ -119,9 +127,14 @@
</svg>
{:else }
<svg class="icon ld-switch-btn__icon"
- viewBox="0 0 20 20">
- <title>dark-auto</title>
+ viewBox="0 0 20 20"><title>light-auto</title>
<g fill="currentColor">
+ <path d="M10 14a4 4 0 1 1 3.465-6"
+ fill-opacity=".2"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"></path>
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
@@ -134,15 +147,36 @@
stroke-linejoin="round"
stroke-width="2"
d="M12.714 16h4.572"></path>
- <path d="M12.146 10.159A2.5 2.5 0 0 1 14.5 8.5h1a2.5 2.5 0 0 1 1.412.441 7 7 0 0 0-4.948-5.657c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036a6.99 6.99 0 0 0 6.427 5.012z"
- fill-opacity=".2"></path>
- <path d="M16.71 8a7.015 7.015 0 0 0-4.746-4.716c.021.237.036.474.036.716a8 8 0 0 1-8 8c-.242 0-.479-.015-.716-.036A7.006 7.006 0 0 0 9 16.929"
- fill="none"
+ <path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
- stroke-width="2"></path>
- <path d="M7 4a.979.979 0 0 1-1-1 1 1 0 0 0-2 0 .979.979 0 0 1-1 1 1 1 0 0 0 0 2 .979.979 0 0 1 1 1 1 1 0 0 0 2 0 .979.979 0 0 1 1-1 1 1 0 0 0 0-2z"></path>
+ stroke-width="2"
+ d="M10 1v1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M16.364 3.636l-1.061 1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 16.364l1.061-1.061"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M1 10h1.5"></path>
+ <path fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M3.636 3.636l1.061 1.061"></path>
</g>
</svg>
{/if}
@@ -371,7 +405,6 @@
<figure class="radius-md inner-glow"
on:click={() => change("system")}>
<svg id="Layer_1"
- class="block radius-inherit"
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
width="70"
diff --git a/apps/web-shared/src/components/user-menu.svelte b/apps/web-shared/src/components/user-menu.svelte
new file mode 100644
index 0000000..c0195f4
--- /dev/null
+++ b/apps/web-shared/src/components/user-menu.svelte
@@ -0,0 +1,99 @@
+<script>
+ import {onMount} from "svelte";
+ import {Menu, MenuItem, MenuItemSeparator} from "./menu";
+
+ let userMenuTrigger;
+ let showUserMenu = false;
+
+ export let avatar = "";
+ export let name;
+ export let secondary = "";
+ let userMenuId;
+
+ onMount(() => {
+ userMenuTrigger = document.getElementById("open-user-menu");
+ });
+</script>
+
+<button class="reset user-menu-control"
+ id="open-user-menu"
+ aria-controls="{userMenuId}"
+ on:click={() => showUserMenu = true}>
+ {#if avatar}
+ <figure class="user-menu-control__img-wrapper radius-50%">
+ <img class="user-menu-control__img"
+ src="{avatar}"
+ alt="User picture">
+ </figure>
+ {/if}
+
+ <div class="margin-x-xs user-menu__meta">
+ <p class="user-menu__meta-title text-sm line-height-1 padding-y-xxxxs font-semibold color-contrast-higher text-truncate">{name}</p>
+ {#if secondary}
+ <p class="text-xs color-contrast-medium line-height-1 padding-bottom-xxxxs">{secondary}</p>
+ {/if}
+ </div>
+
+ <svg class="icon icon--xxs"
+ aria-hidden="true"
+ viewBox="0 0 12 12">
+ <polyline points="1 4 6 9 11 4"
+ fill="none"
+ stroke="currentColor"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"/>
+ </svg>
+</button>
+
+<Menu trigger={userMenuTrigger}
+ bind:id={userMenuId}
+ bind:show="{showUserMenu}">
+ <div slot="options">
+ <MenuItem>
+ <svg class="icon menu__icon"
+ aria-hidden="true"
+ viewBox="0 0 16 16">
+ <circle cx="8"
+ cy="3.5"
+ r="3.5"/>
+ <path d="M14.747,14.15a6.995,6.995,0,0,0-13.494,0A1.428,1.428,0,0,0,1.5,15.4a1.531,1.531,0,0,0,1.209.6H13.288a1.531,1.531,0,0,0,1.209-.6A1.428,1.428,0,0,0,14.747,14.15Z"/>
+ </svg>
+ <span>Profile</span>
+
+ </MenuItem>
+
+ <MenuItem>
+ <svg class="icon menu__icon"
+ aria-hidden="true"
+ viewBox="0 0 16 16">
+ <g fill="currentColor">
+ <path d="M15.135,6.784c-1.303-0.326-1.921-1.818-1.23-2.969c0.322-0.536,0.225-0.998-0.094-1.316l-0.31-0.31 c-0.318-0.318-0.78-0.415-1.316-0.094c-1.152,0.691-2.644,0.073-2.969-1.23C9.065,0.258,8.669,0,8.219,0H7.781 c-0.45,0-0.845,0.258-0.997,0.865c-0.326,1.303-1.818,1.921-2.969,1.23C3.279,1.773,2.816,1.87,2.498,2.188l-0.31,0.31 C1.87,2.816,1.773,3.279,2.095,3.815c0.691,1.152,0.073,2.644-1.23,2.969C0.26,6.935,0,7.33,0,7.781v0.438 c0,0.45,0.258,0.845,0.865,0.997c1.303,0.326,1.921,1.818,1.23,2.969c-0.322,0.536-0.225,0.998,0.094,1.316l0.31,0.31 c0.319,0.319,0.782,0.415,1.316,0.094c1.152-0.691,2.644-0.073,2.969,1.23C6.935,15.742,7.331,16,7.781,16h0.438 c0.45,0,0.845-0.258,0.997-0.865c0.326-1.303,1.818-1.921,2.969-1.23c0.535,0.321,0.997,0.225,1.316-0.094l0.31-0.31 c0.318-0.318,0.415-0.78,0.094-1.316c-0.691-1.152-0.073-2.644,1.23-2.969C15.742,9.065,16,8.669,16,8.219V7.781 C16,7.33,15.74,6.935,15.135,6.784z M8,11c-1.657,0-3-1.343-3-3s1.343-3,3-3s3,1.343,3,3S9.657,11,8,11z"></path>
+ </g>
+ </svg>
+ <span>Settings</span>
+ </MenuItem>
+
+ <MenuItem>
+ <svg class="icon menu__icon"
+ aria-hidden="true"
+ viewBox="0 0 16 16">
+ <circle cx="10.5"
+ cy="2.5"
+ r="2.5"/>
+ <circle cx="5.5"
+ cy="6.5"
+ r="2.5"/>
+ <path d="M15.826,10.124A5.455,5.455,0,0,0,9.46,6.107,3.932,3.932,0,0,1,9.5,6.5,3.97,3.97,0,0,1,8.452,9.176,6.963,6.963,0,0,1,11.539,12h2.829a1.5,1.5,0,0,0,1.458-1.876Z"/>
+ <path d="M10.826,14.124a5.5,5.5,0,0,0-10.652,0A1.5,1.5,0,0,0,1.632,16H9.368a1.5,1.5,0,0,0,1.458-1.876Z"/>
+ </svg>
+ <span>Team</span>
+ </MenuItem>
+
+ <MenuItemSeparator/>
+
+ <MenuItem danger="true" on:click={() => logout_user()}>
+ Logout
+ </MenuItem>
+ </div>
+</Menu>
diff --git a/apps/web-shared/src/lib/helpers.ts b/apps/web-shared/src/lib/helpers.ts
index f2d0cca..4da8254 100644
--- a/apps/web-shared/src/lib/helpers.ts
+++ b/apps/web-shared/src/lib/helpers.ts
@@ -51,9 +51,7 @@ export function get_cookie(name) {
}
export function set_cookie(name, value, baseDomain = window.location.host) {
- let asdf = name + "=" + encodeURIComponent(value) + (baseDomain ? ";domain=" + baseDomain : "");
- console.log(asdf);
- document.cookie = asdf;
+ document.cookie = name + "=" + encodeURIComponent(value) + (baseDomain ? ";domain=" + baseDomain : "");
}
export function unwrap_date_time_from_entry(entry: TimeEntryDto): UnwrappedEntryDateTime {
diff --git a/apps/web-shared/src/lib/session.ts b/apps/web-shared/src/lib/session.ts
index 4f40a17..f729687 100644
--- a/apps/web-shared/src/lib/session.ts
+++ b/apps/web-shared/src/lib/session.ts
@@ -1,5 +1,5 @@
import {Temporal} from "@js-temporal/polyfill";
-import {get_profile_for_active_check} from "./api/user";
+import {get_profile_for_active_check, logout} from "./api/user";
import {is_guid, session_storage_get_json, session_storage_set_json} from "./helpers";
import {SECONDS_BETWEEN_SESSION_CHECK, StorageKeys} from "./configuration";
import type {ISession} from "$shared/lib/models/ISession";
@@ -21,6 +21,12 @@ export async function is_active(forceRefresh: boolean = false): Promise<boolean>
}
}
+export async function end_session(cb: Function): Promise<void> {
+ await logout();
+ clear_session_data();
+ cb();
+}
+
async function call_api(): Promise<boolean> {
console.log("Getting profile data while checking session state");
try {
diff --git a/apps/web-shared/src/styles/components/auto-sized-grid.scss b/apps/web-shared/src/styles/components/auto-sized-grid.scss
new file mode 100644
index 0000000..a3b1be5
--- /dev/null
+++ b/apps/web-shared/src/styles/components/auto-sized-grid.scss
@@ -0,0 +1,56 @@
+@use '../base' as *;
+
+/* --------------------------------
+
+File#: _1_auto-sized-grid
+Title: Auto Sized Grid
+Descr: A grid layout based on CSS Grid where the columns are automatically created according to a min-width value
+Usage: codyhouse.co/license
+
+-------------------------------- */
+
+.grid-auto-xs, .grid-auto-sm, .grid-auto-md, .grid-auto-lg, .grid-auto-xl {
+ display: grid;
+ gap: var(--gap-y, 0px) var(--gap-x, 0px);
+ grid-template-columns: repeat(auto-fit, minmax(var(--col-min-width), 1fr)); // auto add new cols
+}
+
+.grid-auto-xs {
+ --col-min-width: 8rem;
+}
+
+.grid-auto-sm {
+ --col-min-width: 10rem;
+}
+
+.grid-auto-md {
+ --col-min-width: 15rem;
+}
+
+.grid-auto-lg {
+ --col-min-width: 20rem;
+}
+
+.grid-auto-xl {
+ --col-min-width: 25rem;
+}
+
+@each $breakpoint, $value in $breakpoints {
+ @include breakpoint(#{$breakpoint}) {
+ .grid-auto-xs\@#{$breakpoint} {
+ --col-min-width: 8rem;
+ }
+ .grid-auto-sm\@#{$breakpoint} {
+ --col-min-width: 10rem;
+ }
+ .grid-auto-md\@#{$breakpoint} {
+ --col-min-width: 15rem;
+ }
+ .grid-auto-lg\@#{$breakpoint} {
+ --col-min-width: 20rem;
+ }
+ .grid-auto-xl\@#{$breakpoint} {
+ --col-min-width: 25rem;
+ }
+ }
+}
diff --git a/apps/web-shared/src/styles/components/breadcrumbs.scss b/apps/web-shared/src/styles/components/breadcrumbs.scss
new file mode 100644
index 0000000..45bf7c6
--- /dev/null
+++ b/apps/web-shared/src/styles/components/breadcrumbs.scss
@@ -0,0 +1,18 @@
+@use '../base' as *;
+
+/* --------------------------------
+
+File#: _1_breadcrumbs
+Title: Breadcrumbs
+Descr: List of links to help the user move within website structure
+Usage: codyhouse.co/license
+
+-------------------------------- */
+
+.breadcrumbs {}
+
+.breadcrumbs__item {
+ display: inline-block; // flex fallback
+ display: inline-flex;
+ align-items: center;
+}
diff --git a/apps/web-shared/src/styles/components/link-card.scss b/apps/web-shared/src/styles/components/link-card.scss
new file mode 100644
index 0000000..dad4f98
--- /dev/null
+++ b/apps/web-shared/src/styles/components/link-card.scss
@@ -0,0 +1,56 @@
+@use '../base' as *;
+
+/* --------------------------------
+
+File#: _1_link-card
+Title: Link Card
+Descr: Link card for app UI
+Usage: codyhouse.co/license
+
+-------------------------------- */
+
+.link-card {
+ text-decoration: none;
+ color: inherit;
+ box-shadow: var(--inner-glow), var(--shadow-xs);
+
+ &:hover {
+ box-shadow: var(--inner-glow), var(--shadow-sm);
+ }
+}
+
+.link-card__footer {
+ position: relative;
+ overflow: hidden;
+ height: 60px;
+
+ > * {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ > *:last-child {
+ transform: translateY(100%);
+ opacity: 0;
+ }
+}
+
+.link-card:hover {
+ .link-card__footer {
+ > *:first-child {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+
+ > *:last-child {
+ transform: translateY(0);
+ opacity: 1;
+ }
+ }
+}
diff --git a/apps/web-shared/src/styles/components/user-menu.scss b/apps/web-shared/src/styles/components/user-menu.scss
index 1b5c1d5..416655f 100644
--- a/apps/web-shared/src/styles/components/user-menu.scss
+++ b/apps/web-shared/src/styles/components/user-menu.scss
@@ -42,7 +42,7 @@ Usage: codyhouse.co/license
width: var(--profile-figure-size);
height: var(--profile-figure-size);
position: relative;
- transition: opacity 0.2s;
+ //transition: opacity 0.2s;
&::after {
content: '';
@@ -61,7 +61,7 @@ Usage: codyhouse.co/license
opacity: 0;
transform: scale(0.8);
- transition: all 0.2s;
+ //transition: all 0.2s;
}
}
@@ -73,9 +73,9 @@ Usage: codyhouse.co/license
}
.user-menu__meta {
- max-width: 100px;
+ //max-width: 100px;
}
.user-menu__meta-title {
- transition: color 0.2s;
+ //transition: color 0.2s;
}