add basic notifications

This commit is contained in:
Torsten Dittmann
2022-02-02 00:26:22 +01:00
parent 8e09aeedbe
commit 6b91f81941
8 changed files with 216 additions and 73 deletions
+60 -59
View File
@@ -16,20 +16,20 @@
"@testing-library/user-event": "^13.5.0",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.7.0",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^3.4.0",
"jest": "^27.4.7",
"prettier": "^2.4.1",
"prettier-plugin-svelte": "^2.6.0",
"sass": "^1.48.0",
"svelte": "^3.46.2",
"svelte-check": "^2.3.0",
"svelte-jester": "^2.1.5",
"svelte-preprocess": "^4.10.1",
"sass": "^1.49.7",
"svelte": "^3.46.3",
"svelte-check": "^2.4.1",
"svelte-jester": "^2.3.1",
"svelte-preprocess": "^4.10.2",
"ts-jest": "^27.1.3",
"tslib": "^2.3.1",
"typescript": "^4.5.4"
"typescript": "^4.5.5"
}
},
"node_modules/@babel/code-frame": {
@@ -3048,9 +3048,9 @@
}
},
"node_modules/eslint": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.7.0.tgz",
"integrity": "sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz",
"integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.0.5",
@@ -5440,9 +5440,9 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
@@ -6036,10 +6036,11 @@
}
},
"node_modules/sass": {
"version": "1.48.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.48.0.tgz",
"integrity": "sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==",
"version": "1.49.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.7.tgz",
"integrity": "sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -6049,7 +6050,7 @@
"sass": "sass.js"
},
"engines": {
"node": ">=8.9.0"
"node": ">=12.0.0"
}
},
"node_modules/saxes": {
@@ -6335,18 +6336,18 @@
}
},
"node_modules/svelte": {
"version": "3.46.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.2.tgz",
"integrity": "sha512-RXSAtYNefe01Sb1lXtZ2I+gzn3t/h/59hoaRNeRrm8IkMIu6BSiAkbpi41xb+C44x54YKnbk9+dtfs3pM4hECA==",
"version": "3.46.3",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.3.tgz",
"integrity": "sha512-mTOXvv74CVQqJHqoIZDprVfRKVVmYNadXP0VKnOEA54223kLGCr1nMrifS4Zx29qMt/xRB38Eq1D7dDH/fM8fQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/svelte-check": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.3.0.tgz",
"integrity": "sha512-SBKdJyUmxzPmJf/ZPqDSQOoa9JzOcgEpV7u3UaYcgVn7fA0veZ3FA5JgLU8KYtf84Gp6guBVcrC7XKLjJa5SXQ==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.4.1.tgz",
"integrity": "sha512-xhf3ShP5rnRwBokrgTBJ/0cO9QIc1DAVu1NWNRTfCDsDBNjGmkS3HgitgUadRuoMKj1+irZR/yHJ+Uqobnkbrw==",
"dev": true,
"dependencies": {
"chokidar": "^3.4.1",
@@ -6376,12 +6377,12 @@
}
},
"node_modules/svelte-jester": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/svelte-jester/-/svelte-jester-2.1.5.tgz",
"integrity": "sha512-T3JoPsNYltlN2MiVFDxAMOulIO4fztFFI3DhOkzbs1yZQCUFp6sqS6aqusBsIEMVmXaEDW4HO/6bX+CuwuEUSw==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/svelte-jester/-/svelte-jester-2.3.1.tgz",
"integrity": "sha512-Iss/w6EhWRqQ8e3I+UwT4JQCexwnA22jhr+1PcOD8+P99yQh3SsHHK2r2Fvr4bHHjBtvOTgzxhkOyRXP45lj4Q==",
"dev": true,
"engines": {
"node": ">= 12"
"node": ">=14"
},
"peerDependencies": {
"jest": ">= 27",
@@ -6389,9 +6390,9 @@
}
},
"node_modules/svelte-preprocess": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.1.tgz",
"integrity": "sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==",
"version": "4.10.2",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.2.tgz",
"integrity": "sha512-aPpkCreSo8EL/y8kJSa1trhiX0oyAtTjlNNM7BNjRAsMJ8Yy2LtqHt0zyd4pQPXt+D4PzbO3qTjjio3kwOxDlA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -6408,12 +6409,12 @@
"peerDependencies": {
"@babel/core": "^7.10.2",
"coffeescript": "^2.5.1",
"less": "^3.11.3",
"less": "^3.11.3 || ^4.0.0",
"postcss": "^7 || ^8",
"postcss-load-config": "^2.1.0 || ^3.0.0",
"pug": "^3.0.0",
"sass": "^1.26.8",
"stylus": "^0.54.7",
"stylus": "^0.55.0",
"sugarss": "^2.0.0",
"svelte": "^3.23.0",
"typescript": "^4.5.2"
@@ -6679,9 +6680,9 @@
}
},
"node_modules/typescript": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -9518,9 +9519,9 @@
}
},
"eslint": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.7.0.tgz",
"integrity": "sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.8.0.tgz",
"integrity": "sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.0.5",
@@ -11447,9 +11448,9 @@
"dev": true
},
"nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"dev": true
},
"natural-compare": {
@@ -11871,9 +11872,9 @@
}
},
"sass": {
"version": "1.48.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.48.0.tgz",
"integrity": "sha512-hQi5g4DcfjcipotoHZ80l7GNJHGqQS5LwMBjVYB/TaT0vcSSpbgM8Ad7cgfsB2M0MinbkEQQPO9+sjjSiwxqmw==",
"version": "1.49.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.7.tgz",
"integrity": "sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
@@ -12096,15 +12097,15 @@
"dev": true
},
"svelte": {
"version": "3.46.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.2.tgz",
"integrity": "sha512-RXSAtYNefe01Sb1lXtZ2I+gzn3t/h/59hoaRNeRrm8IkMIu6BSiAkbpi41xb+C44x54YKnbk9+dtfs3pM4hECA==",
"version": "3.46.3",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.46.3.tgz",
"integrity": "sha512-mTOXvv74CVQqJHqoIZDprVfRKVVmYNadXP0VKnOEA54223kLGCr1nMrifS4Zx29qMt/xRB38Eq1D7dDH/fM8fQ==",
"dev": true
},
"svelte-check": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.3.0.tgz",
"integrity": "sha512-SBKdJyUmxzPmJf/ZPqDSQOoa9JzOcgEpV7u3UaYcgVn7fA0veZ3FA5JgLU8KYtf84Gp6guBVcrC7XKLjJa5SXQ==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.4.1.tgz",
"integrity": "sha512-xhf3ShP5rnRwBokrgTBJ/0cO9QIc1DAVu1NWNRTfCDsDBNjGmkS3HgitgUadRuoMKj1+irZR/yHJ+Uqobnkbrw==",
"dev": true,
"requires": {
"chokidar": "^3.4.1",
@@ -12126,16 +12127,16 @@
"requires": {}
},
"svelte-jester": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/svelte-jester/-/svelte-jester-2.1.5.tgz",
"integrity": "sha512-T3JoPsNYltlN2MiVFDxAMOulIO4fztFFI3DhOkzbs1yZQCUFp6sqS6aqusBsIEMVmXaEDW4HO/6bX+CuwuEUSw==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/svelte-jester/-/svelte-jester-2.3.1.tgz",
"integrity": "sha512-Iss/w6EhWRqQ8e3I+UwT4JQCexwnA22jhr+1PcOD8+P99yQh3SsHHK2r2Fvr4bHHjBtvOTgzxhkOyRXP45lj4Q==",
"dev": true,
"requires": {}
},
"svelte-preprocess": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.1.tgz",
"integrity": "sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==",
"version": "4.10.2",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.2.tgz",
"integrity": "sha512-aPpkCreSo8EL/y8kJSa1trhiX0oyAtTjlNNM7BNjRAsMJ8Yy2LtqHt0zyd4pQPXt+D4PzbO3qTjjio3kwOxDlA==",
"dev": true,
"requires": {
"@types/pug": "^2.0.4",
@@ -12306,9 +12307,9 @@
}
},
"typescript": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"dev": true
},
"universalify": {
+7 -7
View File
@@ -23,20 +23,20 @@
"@testing-library/user-event": "^13.5.0",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.7.0",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^3.4.0",
"jest": "^27.4.7",
"prettier": "^2.4.1",
"prettier-plugin-svelte": "^2.6.0",
"sass": "^1.48.0",
"svelte": "^3.46.2",
"svelte-check": "^2.3.0",
"svelte-jester": "^2.1.5",
"svelte-preprocess": "^4.10.1",
"sass": "^1.49.7",
"svelte": "^3.46.3",
"svelte-check": "^2.4.1",
"svelte-jester": "^2.3.1",
"svelte-preprocess": "^4.10.2",
"ts-jest": "^27.1.3",
"tslib": "^2.3.1",
"typescript": "^4.5.4"
"typescript": "^4.5.5"
},
"type": "module",
"jest": {
+52
View File
@@ -0,0 +1,52 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import type { Notification } from '../notifications';
const dispatch = createEventDispatcher();
export let type: Notification['type'] = 'info';
export let dismissible = true;
</script>
<article class={type} role="alert" transition:fade>
<div class="text">
<slot />
</div>
{#if dismissible}
<button class="close" on:click={() => dispatch('dismiss')}> x </button>
{/if}
</article>
<style lang="scss">
article {
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.2rem;
display: flex;
align-items: center;
margin: 0 auto 0.5rem auto;
width: 20rem;
}
.error {
background: IndianRed;
}
.success {
background: MediumSeaGreen;
}
.info {
background: SkyBlue;
}
.text {
margin-left: 1rem;
}
button {
color: white;
background: transparent;
border: 0 none;
padding: 0;
margin: 0 0 0 auto;
line-height: 1;
font-size: 1rem;
}
</style>
+34
View File
@@ -0,0 +1,34 @@
<script lang="ts">
import Notification from './notification.svelte';
import { dismissToast, toasts } from '../notifications';
</script>
{#if $toasts}
<section>
{#each $toasts as toast (toast.id)}
<Notification
type={toast.type}
dismissible={toast.dismissible}
on:dismiss={() => dismissToast(toast.id)}
>
{toast.message}
</Notification>
{/each}
</section>
{/if}
<style lang="postcss">
section {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
display: flex;
margin-top: 1rem;
justify-content: center;
flex-direction: column;
z-index: 1000;
}
</style>
+31
View File
@@ -0,0 +1,31 @@
import { writable } from 'svelte/store';
export type Notification = {
id: number;
type: 'success' | 'error' | 'info';
dismissible: boolean;
timeout: number;
message: string;
};
let counter = 0;
export const toasts = writable<Notification[]>([]);
export const dismissToast = (id: number) => {
toasts.update((all) => all.filter((t) => t.id !== id));
};
export const addToast = (notification: Omit<Notification, 'id'>) => {
const defaults = {
id: counter++,
type: 'info',
dismissible: true,
timeout: 3000
};
const n = { ...defaults, ...notification };
toasts.update((all) => [n, ...all]);
if (n.timeout) setTimeout(() => dismissToast(n.id), n.timeout);
};
+2
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import Notifications from '$lib/layout/notifications.svelte';
import { sdkForConsole } from '$lib/stores/sdk';
import { user } from '$lib/stores/user';
import { onMount } from 'svelte';
@@ -20,6 +21,7 @@
});
</script>
<Notifications />
<main class="container">
<slot />
</main>
@@ -24,7 +24,7 @@
<h1>Overview</h1>
<article class="overview">
<p><img src={getAvatar(user.name)} alt={user.name} class="avatar" /></p>
<p class="id" data-tooltip="Click to copy.">{user.$id}</p>
<p>{user.$id}</p>
<p>Member since {user.registration}</p>
<p>{user.emailVerification ? 'Verified' : 'Unverified'}</p>
<p>{user.email}</p>
@@ -54,10 +54,6 @@
flex-direction: column;
align-items: center;
.id {
cursor: pointer;
}
.avatar {
border-radius: 50%;
}
@@ -1,10 +1,34 @@
<script lang="ts">
import { page } from '$app/stores';
import { Pagination, Table, TableCell, TableHeader, TableBody, TableRow } from '$lib/components';
import {
Pagination,
Table,
TableCell,
TableHeader,
TableBody,
TableRow,
Button
} from '$lib/components';
import { sdkForProject } from '$lib/stores/sdk';
const getSessions = () => sdkForProject.users.getSessions($page.params.user);
const deleteSession = async (id: string) => {
try {
await sdkForProject.users.deleteSession($page.params.user, id);
request = getSessions();
} catch (error) {
alert(error.message);
}
};
const deleteAllSessions = async () => {
try {
if (confirm('You really want to delete all sessions?')) {
await sdkForProject.users.deleteSessions($page.params.user);
}
} catch (error) {
alert(error.message);
}
};
let offset = 0;
const limit = 25;
@@ -19,6 +43,7 @@
<TableCell>Country</TableCell>
<TableCell>OS</TableCell>
<TableCell>IP</TableCell>
<TableCell />
</TableHeader>
<TableBody>
{#each response.sessions as session}
@@ -26,10 +51,12 @@
<TableCell>{session.countryName}</TableCell>
<TableCell>{session.osName}</TableCell>
<TableCell>{session.ip}</TableCell>
<TableCell><Button on:click={() => deleteSession(session.$id)}>Logout</Button></TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
<Pagination {limit} bind:offset sum={response.sum} on:change={() => (request = getSessions())} />
<Button on:click={deleteAllSessions}>Logout from all sessions</Button>
{/await}