mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of github.com:appwrite/appwrite-console-poc into feat-trim-text-tooltip
This commit is contained in:
@@ -147,7 +147,7 @@ Adding a new dependency should have vital value on the product with minimum poss
|
||||
│ │ │ │ ├── _create.svelte // Component to Create collections
|
||||
│ │ │ │ └── index.svelte // Entrypoint for "/console/[PROJECT_ID]/database"
|
||||
│ │ │ ├── storage // Storage Service "/console/[PROJECT]/storage"
|
||||
│ │ │ └── users // Users Service "/console/[PROJECT]/users"
|
||||
│ │ │ └── authentication // Users Service "/console/[PROJECT]/authentication"
|
||||
│ │ └──...
|
||||
│ ├── login.svelte // Component for Login "/console/login"
|
||||
│ └── register.svelte // Component for Register "/console/register"
|
||||
|
||||
Generated
+443
-1030
File diff suppressed because it is too large
Load Diff
+16
-16
@@ -8,7 +8,6 @@
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint .",
|
||||
@@ -20,39 +19,40 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aw-labs/appwrite-console": "^1.0.0-0",
|
||||
"@aw-labs/icons": "0.0.0-37",
|
||||
"@aw-labs/ui": "0.0.0-37",
|
||||
"@aw-labs/icons": "0.0.0-52",
|
||||
"@aw-labs/ui": "0.0.0-52",
|
||||
"echarts": "^5.3.3",
|
||||
"tippy.js": "^6.3.7"
|
||||
"tippy.js": "^6.3.7",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.24.0",
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.63",
|
||||
"@sveltejs/kit": "1.0.0-next.390",
|
||||
"@playwright/test": "^1.25.0",
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.64",
|
||||
"@sveltejs/kit": "1.0.0-next.405",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@testing-library/dom": "^8.16.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/svelte": "^3.1.3",
|
||||
"@testing-library/user-event": "^14.3.0",
|
||||
"@types/gtag.js": "^0.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"@vitest/ui": "^0.18.1",
|
||||
"eslint": "^8.20.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"@vitest/ui": "^0.22.0",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"sass": "^1.53.0",
|
||||
"sass": "^1.54.4",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-jester": "^2.3.2",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.0",
|
||||
"vitest": "^0.18.1"
|
||||
"vite": "^3.0.8",
|
||||
"vitest": "^0.22.0"
|
||||
},
|
||||
"type": "module",
|
||||
"pre-commit": [
|
||||
|
||||
Vendored
+1
@@ -2,4 +2,5 @@
|
||||
/// <reference types="@types/gtag.js" />
|
||||
interface Window {
|
||||
GOOGLE_ANALYTICS: string | false;
|
||||
VERCEL_ANALYTICS_ID: string | false;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
<p class="u-flex-basis-100-percent">
|
||||
<slot />
|
||||
<slot name="subtitle" />
|
||||
</p>
|
||||
</header>
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
import type { Buttons } from '../stores/notifications';
|
||||
|
||||
export let dismissible = false;
|
||||
export let type: '' | 'info' | 'success' | 'warning' | 'danger' = 'info';
|
||||
export let type: 'info' | 'success' | 'warning' | 'error' = 'info';
|
||||
export let buttons: Buttons[] = [];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<section class={`alert is-${type} `}>
|
||||
<section
|
||||
class="alert"
|
||||
class:is-success={type === 'success'}
|
||||
class:is-warning={type === 'warning'}
|
||||
class:is-danger={type === 'error'}
|
||||
class:is-info={type === 'info'}>
|
||||
<div class="alert-grid">
|
||||
{#if dismissible}
|
||||
<button
|
||||
@@ -19,7 +24,12 @@
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
<span class="icon-info" aria-hidden="true" />
|
||||
<span
|
||||
class:icon-info={type === 'info'}
|
||||
class:icon-check-circle={type === 'success'}
|
||||
class:icon-exclamation={type === 'warning'}
|
||||
class:icon-exclamation-circle={type === 'error'}
|
||||
aria-hidden="true" />
|
||||
<div class="content">
|
||||
{#if $$slots.title}
|
||||
<h6 class="title">
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import Avatar from './avatar.svelte';
|
||||
export let avatars = [];
|
||||
export let total = avatars.length;
|
||||
export let size = 40;
|
||||
</script>
|
||||
|
||||
<ul class="avatars-group">
|
||||
{#each avatars as av, index}
|
||||
{#if index < 2}
|
||||
<li class="avatars-group-item">
|
||||
<Avatar {size} src={av.img} name={av.name} />
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if total > 2}
|
||||
<li class="avatars-group-item">
|
||||
<div class="avatar">+{total - 2}</div>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
@@ -21,7 +21,7 @@
|
||||
</script>
|
||||
|
||||
<div class="input-text-wrapper is-with-end-button">
|
||||
<input {value} type="text" class="input-text" disabled />
|
||||
<input {value} type="text" class="input-text" readonly />
|
||||
<button
|
||||
type="button"
|
||||
class="input-button"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export let horizontal: 'left' | 'right' = 'right';
|
||||
export let arrowPosition: 'start' | 'center' | 'end' = 'start';
|
||||
export let arrow = true;
|
||||
export let scrollable = false;
|
||||
let parentElement: HTMLDivElement;
|
||||
|
||||
const onBlur = (event: MouseEvent) => {
|
||||
@@ -31,7 +32,10 @@
|
||||
class:is-block-end={position === 'bottom'}
|
||||
class:is-inline-end={horizontal === 'left'}
|
||||
transition:slide={{ duration: 100 }}>
|
||||
<section class="drop-section">
|
||||
<section
|
||||
class:u-overflow-y-auto={scrollable}
|
||||
class:u-max-height-200={scrollable}
|
||||
class="drop-section ">
|
||||
<ul class="drop-list">
|
||||
<slot name="list" />
|
||||
</ul>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
export let icon: string = null;
|
||||
</script>
|
||||
|
||||
<li class="drop-list-item">
|
||||
<li class="drop-list-item" on:click>
|
||||
<a {href} class="drop-button">
|
||||
<span class="text"><slot /></span>
|
||||
{#if icon}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<button on:click class="card is-border-dashed u-grid u-cross-center">
|
||||
<div class="bucket">
|
||||
<div class="u-flex u-flex-vertical u-cross-center">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="u-flex u-flex-vertical u-cross-center">
|
||||
<slot />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -19,8 +19,7 @@ export { default as Collapsible } from './collapsible.svelte';
|
||||
export { default as DropTabs } from './dropTabs.svelte';
|
||||
export { default as DropTabsItem } from './dropTabsItem.svelte';
|
||||
export { default as Avatar } from './avatar.svelte';
|
||||
export { default as SwitchBox } from './switchBox.svelte';
|
||||
export { default as SwitchBoxes } from './switchBoxes.svelte';
|
||||
export { default as AvatarGroup } from './avatarGroup.svelte';
|
||||
export { default as Alert } from './alert.svelte';
|
||||
export { default as Box } from './box.svelte';
|
||||
export { default as Search } from './search.svelte';
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { browser } from '$app/env';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade, fly, type FadeParams, type FlyParams } from 'svelte/transition';
|
||||
import { Alert } from '$lib/components';
|
||||
|
||||
export let show = false;
|
||||
export let size: 'small' | 'big' = null;
|
||||
export let warning = false;
|
||||
let browser = false;
|
||||
//TODO: explore other solutions compatible with testing library
|
||||
onMount(() => {
|
||||
browser = true;
|
||||
});
|
||||
export let error: string = null;
|
||||
export let closable = true;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const transitionFly: FlyParams = {
|
||||
@@ -26,19 +25,22 @@
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBLur = (event: MouseEvent) => {
|
||||
const target: Partial<HTMLElement> = event.target;
|
||||
if (target.hasAttribute('data-curtain')) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
show = false;
|
||||
dispatch('close');
|
||||
if (closable) {
|
||||
show = false;
|
||||
dispatch('close');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Workaround until https://github.com/sveltejs/svelte/issues/3105 is resolved.
|
||||
*/
|
||||
$: if (browser) {
|
||||
if (show) {
|
||||
document.body.classList.add('u-overflow-hidden');
|
||||
@@ -66,16 +68,28 @@
|
||||
<h4 class="heading-level-5">
|
||||
<slot name="header" />
|
||||
</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="x-button"
|
||||
aria-label="Close Modal"
|
||||
title="Close Modal"
|
||||
on:click={closeModal}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
{#if closable}
|
||||
<button
|
||||
type="button"
|
||||
class="x-button"
|
||||
aria-label="Close Modal"
|
||||
title="Close Modal"
|
||||
on:click={closeModal}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
</header>
|
||||
<div class="modal-content">
|
||||
{#if error}
|
||||
<Alert
|
||||
dismissible
|
||||
type="warning"
|
||||
on:dismiss={() => {
|
||||
error = null;
|
||||
}}>
|
||||
{error}
|
||||
</Alert>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
type SwitchBox = {
|
||||
label: string;
|
||||
id: string;
|
||||
src: string;
|
||||
alt: string;
|
||||
href: string;
|
||||
linkText: string;
|
||||
value: boolean;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
wip: boolean;
|
||||
};
|
||||
|
||||
export let box: SwitchBox;
|
||||
|
||||
let { label, id, src, alt, href, linkText, disabled, required, value, wip } = box;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
//TODO: move SwitchBox type outside component
|
||||
</script>
|
||||
|
||||
<li class="card">
|
||||
<label class="switch-box" for={id}>
|
||||
<div class="switch-box-image">
|
||||
<img height="50" width="50" src={src || 'https://via.placeholder.com/50'} {alt} />
|
||||
</div>
|
||||
<span class="switch-box-title">{label}</span>
|
||||
{#if !wip}
|
||||
<a {href} class="link" target="_blank">
|
||||
<span class="text">{linkText || 'Docs'} </span>
|
||||
<span class="icon-link-ext" aria-hidden="true" />
|
||||
</a>
|
||||
<input
|
||||
{id}
|
||||
{disabled}
|
||||
{required}
|
||||
type="checkbox"
|
||||
class="switch"
|
||||
role="switch"
|
||||
bind:checked={value}
|
||||
on:change={() => dispatch('updated', { value, id })} />
|
||||
{/if}
|
||||
</label>
|
||||
</li>
|
||||
@@ -1,13 +0,0 @@
|
||||
<script>
|
||||
import SwitchBox from './switchBox.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let boxes;
|
||||
</script>
|
||||
|
||||
<ul class="grid-box">
|
||||
{#each boxes as box}
|
||||
<SwitchBox {box} on:updated={(e) => dispatch('updated', e.detail)} />
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -7,6 +7,7 @@
|
||||
export let round = false;
|
||||
export let external = false;
|
||||
export let href: string = null;
|
||||
export let fullWidth = false;
|
||||
//TODO: add option to add aria-label to buttons that are only icons
|
||||
</script>
|
||||
|
||||
@@ -20,7 +21,8 @@
|
||||
class:is-only-icon={round}
|
||||
class:is-secondary={secondary}
|
||||
class:is-text={text}
|
||||
class:is-danger={danger}>
|
||||
class:is-danger={danger}
|
||||
class:is-full-width={fullWidth}>
|
||||
<slot />
|
||||
</a>
|
||||
{:else}
|
||||
@@ -32,6 +34,7 @@
|
||||
class:is-secondary={secondary}
|
||||
class:is-danger={danger}
|
||||
class:is-text={text}
|
||||
class:is-full-width={fullWidth}
|
||||
type={submit ? 'submit' : 'button'}>
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<form class="form" on:submit|preventDefault>
|
||||
<form class="form common-section" on:submit|preventDefault>
|
||||
<slot />
|
||||
</form>
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
<script lang="ts">
|
||||
export let type: false | 'success' | 'warning' | 'error' = 'warning';
|
||||
|
||||
let icon = '';
|
||||
switch (type) {
|
||||
case 'success':
|
||||
icon = 'check-circle';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = 'exclamation';
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
icon = 'exclamation-circle';
|
||||
break;
|
||||
|
||||
default:
|
||||
icon = '';
|
||||
break;
|
||||
}
|
||||
export let type: 'success' | 'warning' | 'error' | 'info' | 'neutral' = 'warning';
|
||||
</script>
|
||||
|
||||
<li class={`helper u-${type} u-margin-block-start-8"`}>
|
||||
{#if icon !== ''}
|
||||
<span class={`icon-${icon}`} aria-hidden="true" />
|
||||
<p
|
||||
class="helper u-margin-block-start-12"
|
||||
class:u-info={type === 'info'}
|
||||
class:u-error={type === 'error'}
|
||||
class:u-success={type === 'success'}
|
||||
class:u-warning={type === 'warning'}>
|
||||
{#if type}
|
||||
<span
|
||||
class:icon-info={type === 'info' || type === 'neutral'}
|
||||
class:icon-exclamation-circle={type === 'error'}
|
||||
class:icon-check-circle={type === 'success'}
|
||||
class:icon-exclamation={type === 'warning'}
|
||||
aria-hidden="true" />
|
||||
{/if}
|
||||
<span class="text">
|
||||
<slot />
|
||||
</span>
|
||||
</li>
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -7,11 +7,37 @@
|
||||
export let value = false;
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input {id} {disabled} {required} type="checkbox" bind:checked={value} />
|
||||
<input
|
||||
{id}
|
||||
{disabled}
|
||||
{required}
|
||||
type="checkbox"
|
||||
bind:this={element}
|
||||
bind:checked={value}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
export let placeholder = '';
|
||||
export let required = false;
|
||||
export let autofocus = false;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let unique = true;
|
||||
let bench = '';
|
||||
|
||||
const toggle = async () => {
|
||||
unique = !unique;
|
||||
if (unique) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -13,12 +13,30 @@
|
||||
export let autocomplete = false;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (element.validity.typeMismatch) {
|
||||
error = 'Your email should be formatted as: name@example.com';
|
||||
return;
|
||||
}
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -29,10 +47,14 @@
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{required}
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
type="email"
|
||||
class="input-text"
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input {id} {disabled} {required} bind:files type="file" />
|
||||
<input {id} {disabled} {required} type="file" bind:files />
|
||||
</div>
|
||||
</FormItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -14,12 +14,28 @@
|
||||
export let minlength: number = null;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -35,6 +51,10 @@
|
||||
type="number"
|
||||
class="input-text"
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
@@ -13,8 +13,11 @@
|
||||
export let meter = false;
|
||||
export let autocomplete = false;
|
||||
export let showPasswordButton = false;
|
||||
export let minlength = 8;
|
||||
export let maxlength: number = null;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
let showInPlainText = false;
|
||||
|
||||
onMount(() => {
|
||||
@@ -23,7 +26,24 @@
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
if (element.validity.tooShort) {
|
||||
error = 'Password should contain at least 8 characters';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: strength = value ? value.length * 10 : 0;
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -31,27 +51,32 @@
|
||||
<div class="input-text-wrapper">
|
||||
{#if showInPlainText}
|
||||
<input
|
||||
type="text"
|
||||
{id}
|
||||
name={id}
|
||||
{placeholder}
|
||||
{required}
|
||||
{disabled}
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
{minlength}
|
||||
{maxlength}
|
||||
name={id}
|
||||
type="text"
|
||||
class="input-text"
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
{:else}
|
||||
<input
|
||||
{id}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{required}
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
{minlength}
|
||||
{maxlength}
|
||||
type="password"
|
||||
class="input-text"
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
{/if}
|
||||
|
||||
{#if meter}
|
||||
@@ -67,6 +92,7 @@
|
||||
{/if}
|
||||
{#if showPasswordButton}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showInPlainText = !showInPlainText)}
|
||||
class="show-password-button"
|
||||
aria-label="show password">
|
||||
@@ -77,4 +103,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -13,13 +13,35 @@
|
||||
export let autocomplete = false;
|
||||
export let maxlength: number = null;
|
||||
|
||||
const pattern = String.raw`^\+?[1-9]\d{1,14}$`;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.patternMismatch) {
|
||||
error = "Allowed characters: leading '+' and maximum of 15 digits";
|
||||
return;
|
||||
}
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -31,10 +53,15 @@
|
||||
{disabled}
|
||||
{required}
|
||||
{maxlength}
|
||||
{pattern}
|
||||
type="text"
|
||||
autocomplete={autocomplete ? 'on' : 'off'}
|
||||
type="tel"
|
||||
class="input-text"
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -9,11 +9,40 @@
|
||||
export let name: string;
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<div class="input-text-wrapper">
|
||||
<input {id} {name} {disabled} {required} {value} type="radio" bind:group />
|
||||
<input
|
||||
{id}
|
||||
{name}
|
||||
{disabled}
|
||||
{required}
|
||||
{value}
|
||||
type="radio"
|
||||
bind:group
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -40,16 +40,11 @@
|
||||
{required}
|
||||
type="search"
|
||||
class="input-text"
|
||||
on:input={valueChange}
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:input={valueChange} />
|
||||
<span class="icon-search" aria-hidden="true" />
|
||||
{#if isWithEndButton && value}
|
||||
<button
|
||||
class="x-button"
|
||||
aria-label="Clear search"
|
||||
on:click={() => {
|
||||
value = '';
|
||||
}}>
|
||||
<button class="x-button" aria-label="Clear search" on:click={() => (value = '')}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -11,12 +11,35 @@
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
|
||||
let element: HTMLSelectElement;
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
<label class:u-hide={!showLabel} class="label" for={id}>{label}</label>
|
||||
<div class="select">
|
||||
<select bind:value {id} {required} {disabled}>
|
||||
<select
|
||||
{id}
|
||||
{required}
|
||||
{disabled}
|
||||
bind:this={element}
|
||||
bind:value
|
||||
on:invalid={handleInvalid}>
|
||||
{#each options as option}
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
@@ -25,4 +48,7 @@
|
||||
</select>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let id: string;
|
||||
export let value = false;
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -18,11 +35,16 @@
|
||||
type="checkbox"
|
||||
class="switch"
|
||||
role="switch"
|
||||
bind:this={element}
|
||||
bind:checked={value}
|
||||
on:change />
|
||||
on:change
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
<div class="choice-item-content">
|
||||
<div class="choice-item-title">{label}</div>
|
||||
</div>
|
||||
</label>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
export let tags: string[] = [];
|
||||
export let placeholder = '';
|
||||
export let helper = '';
|
||||
export let autofocus = false;
|
||||
|
||||
let value = '';
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
@@ -48,6 +48,20 @@
|
||||
const removeValue = (value: string) => {
|
||||
tags = tags.filter((tag) => tag !== value);
|
||||
};
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -73,17 +87,18 @@
|
||||
</ul>
|
||||
</div>
|
||||
<input
|
||||
{id}
|
||||
placeholder={!tags.length ? placeholder : ''}
|
||||
type="text"
|
||||
class="tags-input-text"
|
||||
{id}
|
||||
{placeholder}
|
||||
bind:value
|
||||
bind:this={element}
|
||||
on:keydown={handleInput}
|
||||
on:blur={addValue}
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
</div>
|
||||
{#if helper}
|
||||
<p class="helper u-margin-block-start-12">{helper}</p>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -14,12 +14,28 @@
|
||||
export let maxlength: number = null;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -35,6 +51,10 @@
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { FormItem } from '.';
|
||||
import { FormItem, Helper } from '.';
|
||||
|
||||
export let label: string;
|
||||
export let showLabel = true;
|
||||
@@ -13,12 +13,26 @@
|
||||
export let maxlength: number = null;
|
||||
|
||||
let element: HTMLTextAreaElement;
|
||||
let error: string;
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (element.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = element.validationMessage;
|
||||
};
|
||||
|
||||
$: if (value) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormItem>
|
||||
@@ -32,6 +46,10 @@
|
||||
{maxlength}
|
||||
class="input-text"
|
||||
bind:value
|
||||
bind:this={element} />
|
||||
bind:this={element}
|
||||
on:invalid={handleInvalid} />
|
||||
</div>
|
||||
{#if error}
|
||||
<Helper type="warning">{error}</Helper>
|
||||
{/if}
|
||||
</FormItem>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let disabled = false;
|
||||
export let selected = false;
|
||||
export let success = false;
|
||||
export let warning = false;
|
||||
export let danger = false;
|
||||
@@ -18,6 +19,7 @@
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
class="tag"
|
||||
class:is-disabled={disabled}
|
||||
class:is-selected={selected}
|
||||
class:is-success={success}
|
||||
class:is-warning={warning}
|
||||
class:is-danger={danger}
|
||||
@@ -31,6 +33,7 @@
|
||||
type={submit ? 'submit' : 'button'}
|
||||
class="tag"
|
||||
class:is-disabled={disabled}
|
||||
class:is-selected={selected}
|
||||
class:is-success={success}
|
||||
class:is-warning={warning}
|
||||
class:is-danger={danger}
|
||||
@@ -41,6 +44,7 @@
|
||||
<div
|
||||
class="tag"
|
||||
class:is-disabled={disabled}
|
||||
class:is-selected={selected}
|
||||
class:is-success={success}
|
||||
class:is-warning={warning}
|
||||
class:is-danger={danger}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
export function bytesToSize(bytes: number, decimals = 1) {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
export function calculateSize(bytes: number, decimals = 1) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
@@ -11,7 +12,10 @@ export function bytesToSize(bytes: number, decimals = 1) {
|
||||
}
|
||||
|
||||
export function sizeToBytes(value: number, unit: string) {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const index = sizes.indexOf(unit);
|
||||
return value * Math.pow(1024, index);
|
||||
}
|
||||
export function bytesToSize(value: number, unit: string) {
|
||||
const index = sizes.indexOf(unit);
|
||||
return value / Math.pow(1024, index);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { getCLS, getFCP, getFID, getLCP, getTTFB, type Metric } from 'web-vitals';
|
||||
|
||||
type Options = {
|
||||
params:
|
||||
| {
|
||||
[s: string]: string;
|
||||
}
|
||||
| ArrayLike<string>;
|
||||
path: string;
|
||||
analyticsId: string;
|
||||
debug?: boolean;
|
||||
};
|
||||
|
||||
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
|
||||
|
||||
function getConnectionSpeed() {
|
||||
return 'connection' in navigator &&
|
||||
navigator['connection'] &&
|
||||
'effectiveType' in navigator['connection']
|
||||
? navigator['connection']['effectiveType']
|
||||
: '';
|
||||
}
|
||||
|
||||
function sendToAnalytics(metric: Metric, options: Options) {
|
||||
const page = Object.entries(options.params).reduce(
|
||||
(acc, [key, value]) => acc.replace(value, `[${key}]`),
|
||||
options.path
|
||||
);
|
||||
|
||||
const body = {
|
||||
dsn: options.analyticsId,
|
||||
id: metric.id,
|
||||
page,
|
||||
href: location.href,
|
||||
event_name: metric.name,
|
||||
value: metric.value.toString(),
|
||||
speed: getConnectionSpeed()
|
||||
};
|
||||
|
||||
if (options.debug) {
|
||||
console.log('[Analytics]', metric.name, JSON.stringify(body, null, 2));
|
||||
}
|
||||
|
||||
const blob = new Blob([new URLSearchParams(body).toString()], {
|
||||
// This content type is necessary for `sendBeacon`
|
||||
type: 'application/x-www-form-urlencoded'
|
||||
});
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(vitalsUrl, blob);
|
||||
} else
|
||||
fetch(vitalsUrl, {
|
||||
body: blob,
|
||||
method: 'POST',
|
||||
credentials: 'omit',
|
||||
keepalive: true
|
||||
});
|
||||
}
|
||||
|
||||
export function webVitals(options: Options) {
|
||||
try {
|
||||
getFID((metric) => sendToAnalytics(metric, options));
|
||||
getTTFB((metric) => sendToAnalytics(metric, options));
|
||||
getLCP((metric) => sendToAnalytics(metric, options));
|
||||
getCLS((metric) => sendToAnalytics(metric, options));
|
||||
getFCP((metric) => sendToAnalytics(metric, options));
|
||||
} catch (err) {
|
||||
console.error('[Analytics]', err);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<svg width="600" height="400" viewBox="0 0 600 400" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_129_12217)">
|
||||
<path d="M10 173.533L153.942 258L306 171.379L162.912 77L10 173.533Z" fill="url(#paint0_linear_129_12217)"/>
|
||||
<path d="M218 243.774L362.531 325L420 292.164L278.471 201L218 243.774Z" fill="url(#paint1_linear_129_12217)"/>
|
||||
<path d="M153 257.818V138.165C153 134.18 155.145 130.498 158.623 128.512L302.704 46.2306C303.854 45.5738 305.289 46.3982 305.289 47.7157V170.646L390.326 221.35C392.527 222.663 392.565 225.818 390.395 227.182L253.165 313.469C252.078 314.152 250.698 314.178 249.586 313.536L153 257.818Z" fill="url(#paint2_linear_129_12217)"/>
|
||||
<path d="M250.687 307L158 253.842L304.754 170L388 219.268L380.276 224.886L352.384 243.47L250.687 307Z" fill="url(#paint3_linear_129_12217)"/>
|
||||
<path d="M234.034 288.505L186 259.6L310.973 188L353.332 213.152C355.549 214.469 355.558 217.646 353.347 218.974L237.602 288.506C236.505 289.165 235.13 289.164 234.034 288.505Z" fill="url(#paint4_linear_129_12217)"/>
|
||||
<path d="M158 255V139.683C158 136.258 159.832 133.1 162.793 131.421L302.453 52.2238C303.592 51.5777 305 52.4078 305 53.7259V170.554L158 255Z" fill="url(#paint5_linear_129_12217)"/>
|
||||
<path d="M173 213.589V150.081C173 148.241 173.997 146.543 175.61 145.636L279.437 87.2241C280.581 86.5803 282 87.4004 282 88.7056V155.341C282 156.561 281.343 157.687 280.278 158.294L178.144 216.542C175.855 217.847 173 216.208 173 213.589Z" fill="url(#paint6_linear_129_12217)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M335 185.747L183.431 274L183 273.253L334.569 185L335 185.747Z" fill="url(#paint7_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M344 192.747L191.434 281L191 280.253L343.566 192L344 192.747Z" fill="url(#paint8_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M352 197.738L200.431 285L200 284.262L351.569 197L352 197.738Z" fill="url(#paint9_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M359 203.747L209.426 292L209 291.253L358.574 203L359 203.747Z" fill="url(#paint10_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M250.594 279L202 252.763L202.406 252L251 278.237L250.594 279Z" fill="url(#paint11_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M263.588 271L215 245.74L215.412 245L264 270.26L263.588 271Z" fill="url(#paint12_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M339.594 228L291 201.763L291.406 201L340 227.237L339.594 228Z" fill="url(#paint13_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M352.596 220L305 193.769L305.404 193L353 219.231L352.596 220Z" fill="url(#paint14_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M266.6 258L229 237.746L229.4 237L267 257.254L266.6 258Z" fill="url(#paint15_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M279.59 250L241 230.711L241.41 230L280 249.289L279.59 250Z" fill="url(#paint16_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M290.6 244L253 222.782L253.4 222L291 243.218L290.6 244Z" fill="url(#paint17_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M303.568 237L263 215.782L263.432 215L304 236.218L303.568 237Z" fill="url(#paint18_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M315.6 230L278 208.782L278.4 208L316 229.218L315.6 230Z" fill="url(#paint19_radial_129_12217)" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M465.141 239.16V179H466V239.16C466 241.786 464.578 244.208 462.28 245.496L370.422 297L370 296.255L461.859 244.75C463.886 243.614 465.141 241.477 465.141 239.16Z" fill="url(#paint20_radial_129_12217)" fill-opacity="0.6"/>
|
||||
<path d="M187 263.552V260.534L236.798 288.552L357 217V220.448L236.798 292L187 263.552Z" fill="url(#paint21_linear_129_12217)"/>
|
||||
<path d="M361 217.238C361 216.001 361.668 214.86 362.749 214.252L422.877 180.447C425.167 179.159 428 180.81 428 183.432V284.922C428 287.369 426.692 289.631 424.567 290.856L366.155 324.535C363.865 325.856 361 324.207 361 321.568V217.238Z" fill="url(#paint22_linear_129_12217)"/>
|
||||
<path d="M364 220.038C363.989 218.787 364.662 217.629 365.758 217.012L420.407 186.227C421.565 185.575 423 186.405 423 187.725V282.259C423 283.479 422.351 284.607 421.292 285.227L367.442 316.758C366.291 317.433 364.836 316.616 364.825 315.29L364 220.038Z" fill="url(#paint23_linear_129_12217)"/>
|
||||
<path d="M373 253.14V225.174C373 224.549 373.338 223.973 373.883 223.671L412.733 202.108C413.302 201.792 414 202.206 414 202.86V229.713C414 230.325 413.676 230.891 413.15 231.198L374.283 253.883C373.714 254.215 373 253.802 373 253.14Z" fill="url(#paint24_linear_129_12217)"/>
|
||||
<path d="M373 268V263.339L414 240V244.349L373 268Z" fill="url(#paint25_linear_129_12217)"/>
|
||||
<path d="M373 275.168V272.547C373 272.254 373.167 271.982 373.439 271.832L405.649 254.117C406.246 253.789 407 254.188 407 254.832V257.163C407 257.455 406.836 257.725 406.567 257.875L374.356 275.88C373.76 276.213 373 275.814 373 275.168Z" fill="url(#paint26_linear_129_12217)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M350.379 64L431 106.214L430.621 107L350 64.7858L350.379 64Z" fill="url(#paint27_radial_129_12217)" fill-opacity="0.6"/>
|
||||
<path d="M438 82.4701C438 79.8793 440.758 78.2046 443.08 79.3855L477.101 96.6878C478.267 97.2806 479 98.4718 479 99.7725V191.53C479 194.128 476.227 195.802 473.905 194.607L439.884 177.091C438.726 176.496 438 175.309 438 174.015V82.4701Z" fill="url(#paint28_linear_129_12217)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M447.249 86.3945L439 82V168.114C439 169.373 439.674 170.533 440.761 171.146L469.904 187.557C472.19 188.844 475 187.172 475 184.525V103.272C475 101.984 474.295 100.802 473.168 100.202L467.554 97.2114L467.633 98.8586C467.665 99.5272 466.968 99.9788 466.381 99.67L447.843 89.9166C447.573 89.7742 447.397 89.4971 447.382 89.1888L447.249 86.3945Z" fill="url(#paint29_linear_129_12217)"/>
|
||||
<path d="M470 140.151V112.523C470 111.881 469.637 111.293 469.062 111.006L443.228 98.0907C442.664 97.8086 442 98.2187 442 98.8491V125.406C442 126.029 442.341 126.601 442.889 126.898L468.747 140.896C469.313 141.203 470 140.793 470 140.151Z" fill="url(#paint30_linear_129_12217)"/>
|
||||
<path d="M470 154V149.193L442 133V137.485L470 154Z" fill="url(#paint31_linear_129_12217)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M282.856 95V291.68C282.856 296.714 288.341 299.806 292.619 297.186L342.554 266.594L343 267.328L293.065 297.92C288.216 300.89 282 297.385 282 291.68V95H282.856Z" fill="url(#paint32_radial_129_12217)" fill-opacity="0.6"/>
|
||||
<path d="M183.58 201.297C185.281 197.918 183.58 189.262 188.318 182.586C189.31 181.187 194.053 190.383 193.895 187.113C193.737 183.844 197.334 174.219 200.342 172.5C201.688 171.731 202.061 175.939 202.491 175.939C202.491 175.939 204.234 158.747 208.078 158.747C208.508 158.747 208.508 163.474 208.938 163.475C209.368 163.475 211.517 154.449 214.525 154.449C214.955 154.449 218.199 161.765 219.683 162.615C222.049 156.701 225.38 162.814 226.356 163.449L226.56 163.475C232.577 157.811 236.445 169.062 236.445 169.062L183.58 201.297Z" fill="url(#paint33_linear_129_12217)"/>
|
||||
<mask id="mask0_129_12217" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="193" y="71" width="108" height="132">
|
||||
<path d="M193 199.536V135.051C193 133.183 193.988 131.46 195.586 130.538L298.46 71.2275C299.594 70.5739 301 71.4066 301 72.7318V140.393C301 141.631 300.349 142.774 299.294 143.391L198.097 202.535C195.829 203.86 193 202.196 193 199.536Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_129_12217)">
|
||||
<path d="M247.131 126.667L250.131 125V162.5L247.131 165V126.667Z" fill="url(#paint34_linear_129_12217)"/>
|
||||
<path d="M252.131 139.655L255.131 138V159.517L252.131 162V139.655Z" fill="url(#paint35_linear_129_12217)"/>
|
||||
<path d="M262.131 132.724L265.131 131V153.414L262.131 156V132.724Z" fill="url(#paint36_linear_129_12217)"/>
|
||||
<path d="M267.131 128.793L270.131 127V150.31L267.131 153V128.793Z" fill="url(#paint37_linear_129_12217)"/>
|
||||
<path d="M272.131 136.966L275.131 136V148.552L272.131 150V136.966Z" fill="url(#paint38_linear_129_12217)"/>
|
||||
<path d="M257 126.744L260 125V156.385L257 159V126.744Z" fill="url(#paint39_linear_129_12217)"/>
|
||||
</g>
|
||||
<path d="M181 152.019L212 135V139.981L181 157V152.019Z" fill="url(#paint40_linear_129_12217)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M122.675 167.304L11 98.7355L11.4477 98L123.123 166.569C127.824 169.455 127.765 176.334 123.014 179.138L59.7078 216.503C55.4736 219.002 55.4731 225.151 59.7069 227.651L307.585 374.008C309.56 375.174 312.002 375.198 314 374.071L439.269 303.399C441.548 302.113 444.337 302.15 446.581 303.495L638 418.26L637.56 419L446.142 304.236C444.161 303.048 441.7 303.016 439.69 304.15L314.42 374.823C312.156 376.1 309.388 376.072 307.15 374.751L59.2719 228.394C54.4737 225.561 54.4742 218.592 59.273 215.759L122.579 178.395C126.771 175.921 126.824 169.851 122.675 167.304Z" fill="url(#paint41_radial_129_12217)"/>
|
||||
<path d="M282 196.401L299 186V206.498L282 217V196.401Z" fill="url(#paint42_linear_129_12217)"/>
|
||||
<path d="M282 196.401L265 186V206.498L282 217V196.401Z" fill="url(#paint43_linear_129_12217)"/>
|
||||
<path d="M265 186L281.854 176L299 186L282 196.5L265 186Z" fill="url(#paint44_linear_129_12217)"/>
|
||||
<path d="M296.501 41.9936L317 29.8677V53.7664L296.501 66.01V41.9936Z" fill="url(#paint45_linear_129_12217)"/>
|
||||
<path d="M296.501 41.9936L276.003 29.8677V53.7664L296.501 66.01V41.9936Z" fill="url(#paint46_linear_129_12217)"/>
|
||||
<path d="M276 29.9017L296.322 18L316.997 29.9017L296.322 42.2747L276 29.9017Z" fill="url(#paint47_linear_129_12217)"/>
|
||||
<path d="M343.042 77.4135L351.085 72.6561V82.0323L343.042 86.8359V77.4135Z" fill="url(#paint48_linear_129_12217)"/>
|
||||
<path d="M343.042 77.4135L335 72.6561V82.0323L343.042 86.8359V77.4135Z" fill="url(#paint49_linear_129_12217)"/>
|
||||
<path d="M335 72.6695L342.974 68.0001L351.085 72.6695L342.974 77.5239L335 72.6695Z" fill="url(#paint50_linear_129_12217)"/>
|
||||
<path d="M407.741 32.6788L411.166 30.653V34.6457L407.741 36.6913V32.6788Z" fill="url(#paint51_linear_129_12217)"/>
|
||||
<path d="M407.741 32.6788L404.316 30.653V34.6457L407.741 36.6913V32.6788Z" fill="url(#paint52_linear_129_12217)"/>
|
||||
<path d="M404.316 30.6586L407.712 28.6702L411.166 30.6586L407.712 32.7258L404.316 30.6586Z" fill="url(#paint53_linear_129_12217)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_129_12217" x1="219.72" y1="247.226" x2="126.879" y2="179.065" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_129_12217" x1="433.724" y1="331.913" x2="347.918" y2="267.283" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_129_12217" x1="-344.968" y1="220.356" x2="699.378" y2="354.525" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3E435A"/>
|
||||
<stop offset="1" stop-color="#3C4158"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_129_12217" x1="383" y1="158" x2="52.5" y2="299.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#1B1B28"/>
|
||||
<stop offset="1" stop-color="#282A3B" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_129_12217" x1="294.5" y1="238" x2="201" y2="327.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#373B4D"/>
|
||||
<stop offset="1" stop-color="#373B4D" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_129_12217" x1="424.5" y1="65.5" x2="17.3698" y2="339.823" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#1B1B28"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_129_12217" x1="170.425" y1="205.425" x2="383.489" y2="101.071" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#373B4D"/>
|
||||
<stop offset="1" stop-color="#2C3041"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint7_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(259 229.5) rotate(90) scale(44.1266 75.7844)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint8_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(267.5 236.5) rotate(90) scale(44.1266 76.283)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint9_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(276 241) rotate(90) scale(43.6308 75.7844)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint10_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(284 247.5) rotate(90) scale(44.1266 74.7872)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint11_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(226.5 265.5) rotate(90) scale(13.1183 24.2972)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint12_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(239.5 258) rotate(90) scale(12.63 24.2939)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint13_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(315.5 214.5) rotate(90) scale(13.1183 24.2972)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint14_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(329 206.5) rotate(90) scale(13.1157 23.7981)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint15_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(248 247.5) rotate(90) scale(10.1268 18.8)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint16_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(260.5 240) rotate(90) scale(9.64456 19.2948)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint17_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(272 233) rotate(90) scale(10.609 18.8)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint18_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(283.5 226) rotate(90) scale(10.609 20.2842)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint19_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(297 219) rotate(90) scale(10.609 18.8)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint20_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(473.302 237.6) rotate(145.202) scale(50.2171 40.4795)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint21_linear_129_12217" x1="193.5" y1="347.5" x2="668.828" y2="138.887" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#282A3B"/>
|
||||
<stop offset="1" stop-color="#2E3453"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint22_linear_129_12217" x1="412.538" y1="138.582" x2="397.582" y2="333.082" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#292D44"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint23_linear_129_12217" x1="412.585" y1="126.195" x2="421.125" y2="332.925" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#25283C"/>
|
||||
<stop offset="1" stop-color="#282A3B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint24_linear_129_12217" x1="373" y1="236.174" x2="431.929" y2="221.119" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#34384A"/>
|
||||
<stop offset="1" stop-color="#454B5D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint25_linear_129_12217" x1="373" y1="240" x2="435.332" y2="266.182" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#373B4D"/>
|
||||
<stop offset="1" stop-color="#333A56"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint26_linear_129_12217" x1="365.395" y1="276.638" x2="438.324" y2="274.214" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#373B4D"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint27_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(395.354 82.4219) rotate(90) scale(24.1852 46.1891)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint28_linear_129_12217" x1="506.043" y1="-487.649" x2="460.435" y2="247.086" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2D3147"/>
|
||||
<stop offset="1" stop-color="#34384B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint29_linear_129_12217" x1="391" y1="-559.887" x2="466.038" y2="267.468" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#282A3B"/>
|
||||
<stop offset="1" stop-color="#282A3B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint30_linear_129_12217" x1="484.424" y1="133.942" x2="432.5" y2="133.847" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F5769"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint31_linear_129_12217" x1="470" y1="125.125" x2="407.406" y2="150.451" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#373B4D"/>
|
||||
<stop offset="1" stop-color="#383F5E"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint32_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(228.927 264.381) rotate(-41.8532) scale(239.041 92.135)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint33_linear_129_12217" x1="198.756" y1="161.95" x2="211.336" y2="187.777" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F5769"/>
|
||||
<stop offset="1" stop-color="#373B4D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint34_linear_129_12217" x1="248.631" y1="125" x2="248.631" y2="165" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4A5163"/>
|
||||
<stop offset="1" stop-color="#3F4456"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint35_linear_129_12217" x1="253.631" y1="138" x2="253.631" y2="162" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4A5163"/>
|
||||
<stop offset="1" stop-color="#3F4456"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint36_linear_129_12217" x1="263.631" y1="131" x2="263.631" y2="156" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4A5163"/>
|
||||
<stop offset="1" stop-color="#3F4456"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint37_linear_129_12217" x1="268.631" y1="127" x2="268.631" y2="153" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4A5163"/>
|
||||
<stop offset="1" stop-color="#3F4456"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint38_linear_129_12217" x1="273.631" y1="136" x2="273.631" y2="150" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4A5163"/>
|
||||
<stop offset="1" stop-color="#3F4456"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint39_linear_129_12217" x1="258.5" y1="125" x2="258.5" y2="159" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4A5163"/>
|
||||
<stop offset="1" stop-color="#3F4456"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint40_linear_129_12217" x1="163.347" y1="162.396" x2="216.261" y2="156.595" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint41_radial_129_12217" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(201.766 312.594) rotate(29.046) scale(310.731 612.014)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint42_linear_129_12217" x1="339.926" y1="175.17" x2="289.175" y2="215.325" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0.53"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint43_linear_129_12217" x1="251.463" y1="186.24" x2="270.436" y2="227.113" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0.16"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint44_linear_129_12217" x1="267.519" y1="186.008" x2="308.759" y2="186.008" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0.49"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint45_linear_129_12217" x1="366.349" y1="17.2409" x2="306.751" y2="66.01" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#AC2D5A"/>
|
||||
<stop offset="1" stop-color="#97325D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint46_linear_129_12217" x1="259.68" y1="30.1474" x2="281.317" y2="78.3572" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#7C335B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint47_linear_129_12217" x1="279.037" y1="30.1473" x2="328.765" y2="30.1473" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#DD2E62"/>
|
||||
<stop offset="1" stop-color="#B62D5C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint48_linear_129_12217" x1="370.446" y1="67.7021" x2="347.064" y2="86.8359" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A12D59"/>
|
||||
<stop offset="1" stop-color="#A1315E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint49_linear_129_12217" x1="328.596" y1="72.7658" x2="337.085" y2="91.6801" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B32E5C"/>
|
||||
<stop offset="1" stop-color="#79335A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint50_linear_129_12217" x1="336.192" y1="72.7659" x2="355.702" y2="72.7659" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#BE2D5D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint51_linear_129_12217" x1="419.411" y1="28.5434" x2="409.454" y2="36.6913" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A3315E"/>
|
||||
<stop offset="1" stop-color="#96325D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint52_linear_129_12217" x1="401.589" y1="30.6997" x2="405.204" y2="38.7541" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9D2D58"/>
|
||||
<stop offset="1" stop-color="#6F3359"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint53_linear_129_12217" x1="404.824" y1="30.6997" x2="413.132" y2="30.6997" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C42E5E"/>
|
||||
<stop offset="1" stop-color="#AF2D5B"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_129_12217">
|
||||
<rect width="600" height="400" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,253 @@
|
||||
<svg width="598" height="382" viewBox="0 0 598 382" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 157.8L151.237 246L311 155.55L160.661 57.0001L0 157.8Z" fill="url(#paint0_linear_129_11322)"/>
|
||||
<path d="M207 221.153L357.8 305L420 274.673L270.095 177L207 221.153Z" fill="url(#paint1_linear_129_11322)"/>
|
||||
<path d="M150.677 245.975V121.27C150.677 116.618 153.163 112.32 157.196 110.001L306.264 24.2769C307.597 23.5101 309.261 24.4726 309.261 26.0107V154.565L397.247 207.394C399.799 208.927 399.843 212.611 397.328 214.203L255.191 304.199C253.931 304.996 252.331 305.027 251.042 304.278L150.677 245.975Z" fill="url(#paint2_linear_129_11322)"/>
|
||||
<path d="M252.352 296.964L155.343 241.722L308.941 154.594L396.07 205.793L387.986 211.632L358.793 230.944L252.352 296.964Z" fill="#FCFCFF"/>
|
||||
<path d="M235.904 277.736L185.883 247.32L316.576 171.66L360.296 197.892C362.877 199.441 362.887 203.179 360.313 204.741L240.058 277.738C238.782 278.513 237.18 278.512 235.904 277.736Z" fill="white"/>
|
||||
<path d="M155.343 242.171V122.619C155.343 118.644 157.487 114.979 160.951 113.031L306.859 30.9665C308.192 30.2166 309.839 31.1801 309.839 32.7097V154.145L155.343 242.171Z" fill="url(#paint3_linear_129_11322)"/>
|
||||
<path d="M160.456 231.842V121.821C160.456 120.38 161.23 119.051 162.484 118.341L297.772 41.677C299.105 40.9215 300.758 41.8846 300.758 43.417V150.552L160.456 231.842Z" fill="url(#paint4_linear_129_11322)"/>
|
||||
<path d="M171.511 199.329V133.399C171.511 131.238 172.673 129.245 174.552 128.179L282.6 66.9129C283.934 66.1569 285.587 67.12 285.587 68.6527V138.357C285.587 139.788 284.822 141.111 283.58 141.824L177.505 202.797C174.838 204.33 171.511 202.405 171.511 199.329Z" fill="url(#paint5_linear_129_11322)"/>
|
||||
<mask id="mask0_129_11322" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="171" y="163" width="211" height="125">
|
||||
<path d="M253.622 286.693L171.508 238.894L301.682 163.706L379.872 210.034C382.511 211.598 382.48 215.428 379.816 216.949L257.618 286.71C256.378 287.417 254.856 287.411 253.622 286.693Z" fill="#F9F9F9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_129_11322)">
|
||||
<path d="M181.711 261.493L340.083 169.658" stroke="url(#paint6_radial_129_11322)"/>
|
||||
<path d="M190.642 268.721L349.013 176.886" stroke="url(#paint7_radial_129_11322)"/>
|
||||
<path d="M199.567 273.822L357.939 181.988" stroke="url(#paint8_radial_129_11322)"/>
|
||||
<path d="M208.073 280.625L366.445 188.791" stroke="url(#paint9_radial_129_11322)"/>
|
||||
<path d="M201.27 239.81L252.076 267.02" stroke="url(#paint10_radial_129_11322)"/>
|
||||
<path d="M216.574 232.157L266.743 259.154" stroke="url(#paint11_radial_129_11322)"/>
|
||||
<path d="M294.805 186.24L345.611 213.45" stroke="url(#paint12_radial_129_11322)"/>
|
||||
<path d="M310.11 178.587L360.279 205.584" stroke="url(#paint13_radial_129_11322)"/>
|
||||
<path d="M230.181 223.229L270.146 244.699" stroke="url(#paint14_radial_129_11322)"/>
|
||||
<path d="M242.511 216.001L282.476 237.471" stroke="url(#paint15_radial_129_11322)"/>
|
||||
<path d="M254.84 208.773L294.805 230.244" stroke="url(#paint16_radial_129_11322)"/>
|
||||
<path d="M266.746 201.545L306.711 223.016" stroke="url(#paint17_radial_129_11322)"/>
|
||||
<path d="M280.773 194.318L320.738 215.788" stroke="url(#paint18_radial_129_11322)"/>
|
||||
</g>
|
||||
<path d="M462 159V221.398C462 224.296 460.433 226.967 457.904 228.381L362 282" stroke="url(#paint19_radial_129_11322)" stroke-opacity="0.6"/>
|
||||
<path d="M186 250.414V247.276L238.141 276.414L364 202V205.586L238.141 280L186 250.414Z" fill="url(#paint20_linear_129_11322)"/>
|
||||
<path d="M356.547 199.657C356.547 196.769 358.103 194.105 360.619 192.688L414.681 162.215C420.014 159.209 426.609 163.063 426.609 169.184V267.568C426.609 270.425 425.086 273.065 422.613 274.495L368.55 305.744C363.217 308.827 356.547 304.978 356.547 298.818V199.657Z" fill="url(#paint21_linear_129_11322)"/>
|
||||
<path d="M361.936 202.379C361.936 200.935 362.715 199.603 363.974 198.894L414.359 170.526C417.025 169.025 420.321 170.951 420.321 174.011V264.986C420.321 266.415 419.559 267.735 418.321 268.45L367.936 297.541C365.269 299.081 361.936 297.157 361.936 294.077V202.379Z" fill="url(#paint22_linear_129_11322)"/>
|
||||
<path d="M370.021 234.596V205.632C370.021 204.904 370.416 204.233 371.054 203.881L411.652 181.462C412.319 181.094 413.136 181.576 413.136 182.337V210.144C413.136 210.857 412.756 211.515 412.14 211.873L371.523 235.461C370.856 235.848 370.021 235.367 370.021 234.596Z" fill="url(#paint23_linear_129_11322)"/>
|
||||
<path d="M370.021 249.806V244.872L413.136 220.165V224.769L370.021 249.806Z" fill="url(#paint24_linear_129_11322)"/>
|
||||
<path d="M370.021 257.918V255.322C370.021 254.969 370.207 254.642 370.511 254.462L402.644 235.431C403.31 235.036 404.153 235.516 404.153 236.291V238.575C404.153 238.926 403.97 239.251 403.669 239.432L371.536 258.775C370.87 259.176 370.021 258.696 370.021 257.918Z" fill="url(#paint25_linear_129_11322)"/>
|
||||
<path d="M435 91L349 48" stroke="url(#paint26_radial_129_11322)" stroke-opacity="0.6"/>
|
||||
<path d="M435.594 61.432C435.594 58.4417 438.755 56.5087 441.417 57.8717L475.634 75.3931C476.97 76.0774 477.811 77.4522 477.811 78.9535V173.165C477.811 176.164 474.632 178.097 471.97 176.717L437.753 158.979C436.426 158.292 435.594 156.922 435.594 155.428V61.432Z" fill="url(#paint27_linear_129_11322)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M445.135 64.8297L436.49 60.2792V149.213C436.49 150.665 437.277 152.003 438.545 152.709L468.271 169.25C470.937 170.734 474.216 168.806 474.216 165.755V82.5532C474.216 81.0682 473.393 79.7054 472.079 79.0137L466.413 76.031L466.487 77.5599C466.524 78.3307 465.711 78.8513 465.027 78.4954L445.809 68.5041C445.493 68.3399 445.289 68.0204 445.271 67.665L445.135 64.8297Z" fill="url(#paint28_linear_129_11322)"/>
|
||||
<path d="M469.727 119.68V90.7135C469.727 89.9561 469.299 89.2636 468.621 88.9248L441.532 75.3749C440.867 75.0423 440.085 75.5258 440.085 76.2693V104.112C440.085 104.846 440.487 105.521 441.132 105.871L468.25 120.559C468.917 120.92 469.727 120.438 469.727 119.68Z" fill="url(#paint29_linear_129_11322)"/>
|
||||
<path d="M469.727 134.833V129.898L440.085 113.275V117.879L469.727 134.833Z" fill="url(#paint30_linear_129_11322)"/>
|
||||
<path d="M358 262.247L307.192 293.501C301.861 296.779 295 292.945 295 286.687V82.0001" stroke="url(#paint31_radial_129_11322)" stroke-opacity="0.6"/>
|
||||
<path d="M179.149 190.523C180.927 186.993 179.149 177.948 184.1 170.971C185.137 169.51 190.094 179.119 189.928 175.702C189.763 172.286 193.521 162.229 196.665 160.432C198.071 159.629 198.461 164.025 198.911 164.025C198.911 164.025 200.732 146.06 204.749 146.06C205.198 146.06 205.198 151.001 205.647 151.001C206.096 151.001 208.342 141.569 211.486 141.569C211.935 141.569 215.324 149.214 216.875 150.102C219.347 143.922 222.828 150.31 223.848 150.974L224.061 151.001C230.349 145.083 234.391 156.839 234.391 156.839L179.149 190.523Z" fill="#D2EFF7"/>
|
||||
<mask id="mask1_129_11322" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="201" y="36" width="115" height="138">
|
||||
<path d="M201.152 169.238V103.308C201.152 101.148 202.314 99.1541 204.193 98.0886L312.241 36.8221C313.575 36.0661 315.228 37.0292 315.228 38.5619V108.266C315.228 109.698 314.463 111.02 313.221 111.734L207.146 172.706C204.479 174.239 201.152 172.314 201.152 169.238Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_129_11322)">
|
||||
<path d="M245.166 112.377L248.759 110.58V151.001L245.166 153.695V112.377Z" fill="#D2EFF7"/>
|
||||
<path d="M250.555 125.85L254.148 124.054V147.408L250.555 150.103V125.85Z" fill="#D2EFF7"/>
|
||||
<path d="M261.334 118.664L264.927 116.868V140.222L261.334 142.917V118.664Z" fill="#D2EFF7"/>
|
||||
<path d="M266.723 115.071L270.316 113.275V136.629L266.723 139.324V115.071Z" fill="#D2EFF7"/>
|
||||
<path d="M272.113 123.248L275.706 122.257V135.142L272.113 136.629V123.248Z" fill="#D2EFF7"/>
|
||||
<path d="M255.944 113.275L259.537 111.479V143.815L255.944 146.51V113.275Z" fill="#D2EFF7"/>
|
||||
</g>
|
||||
<path d="M180 135.566L213 117V122.434L180 141V135.566Z" fill="url(#paint32_linear_129_11322)"/>
|
||||
<path d="M10 80.0001L119.757 147.082C124.913 150.233 124.848 157.744 119.637 160.805L59.8377 195.938C54.5747 199.03 54.5741 206.64 59.8365 209.733L304.839 353.727C307.292 355.17 310.328 355.2 312.809 353.806L436.612 284.282C439.11 282.879 442.168 282.919 444.629 284.387L635 398" stroke="url(#paint33_radial_129_11322)"/>
|
||||
<path d="M294.501 23.9936L315 11.8677V35.7664L294.501 48.01V23.9936Z" fill="url(#paint34_linear_129_11322)"/>
|
||||
<path d="M294.501 23.9936L274.003 11.8677V35.7664L294.501 48.01V23.9936Z" fill="url(#paint35_linear_129_11322)"/>
|
||||
<path d="M274 11.9017L294.322 0L314.997 11.9017L294.322 24.2747L274 11.9017Z" fill="url(#paint36_linear_129_11322)"/>
|
||||
<path d="M341.042 59.4135L349.085 54.6561V64.0323L341.042 68.8359V59.4135Z" fill="url(#paint37_linear_129_11322)"/>
|
||||
<path d="M341.042 59.4135L333 54.6561V64.0323L341.042 68.8359V59.4135Z" fill="url(#paint38_linear_129_11322)"/>
|
||||
<path d="M333 54.6695L340.974 50L349.085 54.6695L340.974 59.5238L333 54.6695Z" fill="url(#paint39_linear_129_11322)"/>
|
||||
<path d="M405.741 14.6788L409.166 12.6529V16.6457L405.741 18.6913V14.6788Z" fill="url(#paint40_linear_129_11322)"/>
|
||||
<path d="M405.741 14.6788L402.316 12.6529V16.6457L405.741 18.6913V14.6788Z" fill="url(#paint41_linear_129_11322)"/>
|
||||
<path d="M402.316 12.6586L405.712 10.6702L409.166 12.6586L405.712 14.7258L402.316 12.6586Z" fill="url(#paint42_linear_129_11322)"/>
|
||||
<path d="M294.442 177.447L311.989 167.067V187.524L294.442 198.005V177.447Z" fill="url(#paint43_linear_129_11322)"/>
|
||||
<path d="M294.442 177.447L276.895 167.067V187.524L294.442 198.005V177.447Z" fill="url(#paint44_linear_129_11322)"/>
|
||||
<path d="M276.895 167.096L294.291 156.908L311.988 167.096L294.291 177.687L276.895 167.096Z" fill="url(#paint45_linear_129_11322)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_129_11322" x1="225.733" y1="253.2" x2="123.169" y2="163.028" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_129_11322" x1="432.082" y1="312.136" x2="343.25" y2="244.506" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_129_11322" x1="-1458.33" y1="-430.156" x2="719.884" y2="345.468" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A3A8BD"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_129_11322" x1="-1454.58" y1="-433.23" x2="718.1" y2="364.537" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_129_11322" x1="-604.56" y1="227.351" x2="369.421" y2="145.85" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A3A8BD"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_129_11322" x1="168.817" y1="191.421" x2="392.477" y2="82.735" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint6_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(260.897 215.576) rotate(90) scale(45.9172 79.1859)">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint7_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(269.828 222.803) rotate(90) scale(45.9172 79.1859)">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint8_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(278.753 227.905) rotate(90) scale(45.9172 79.1859)">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint9_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(287.259 234.708) rotate(90) scale(45.9172 79.1859)">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint10_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(226.673 253.415) rotate(90) scale(13.6051 25.4033)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint11_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(241.659 245.656) rotate(90) scale(13.4988 25.0844)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint12_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(320.208 199.845) rotate(90) scale(13.6051 25.4033)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint13_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(335.194 192.086) rotate(90) scale(13.4988 25.0844)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint14_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(250.164 233.964) rotate(90) scale(10.7353 19.9825)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint15_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(262.493 226.736) rotate(90) scale(10.7353 19.9825)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint16_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(274.823 219.508) rotate(90) scale(10.7353 19.9825)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint17_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(286.728 212.281) rotate(90) scale(10.7353 19.9825)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint18_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(300.756 205.053) rotate(90) scale(10.7353 19.9825)">
|
||||
<stop stop-color="#F7F7F7"/>
|
||||
<stop offset="1" stop-color="#F7F7F7" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint19_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(470.108 220.276) rotate(145.278) scale(52.6119 42.3677)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint20_linear_129_11322" x1="221.061" y1="320.345" x2="326.012" y2="233.996" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint21_linear_129_11322" x1="376.757" y1="-252.755" x2="394.722" y2="318.521" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F3F3F3"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint22_linear_129_11322" x1="411.339" y1="102.496" x2="413.569" y2="465.382" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint23_linear_129_11322" x1="354.751" y1="228.249" x2="444.124" y2="227.985" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint24_linear_129_11322" x1="370.021" y1="220.165" x2="435.698" y2="247.569" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint25_linear_129_11322" x1="362.386" y1="259.687" x2="435.611" y2="257.427" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint26_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(397.178 66.3646) rotate(90) scale(24.6354 49.2708)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint27_linear_129_11322" x1="505.656" y1="-530.309" x2="458.05" y2="231.393" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint28_linear_129_11322" x1="455.353" y1="32.9542" x2="462.949" y2="252.004" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E8E9F0"/>
|
||||
<stop offset="1" stop-color="#FCFCFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint29_linear_129_11322" x1="484.997" y1="113.275" x2="395.623" y2="113.011" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint30_linear_129_11322" x1="469.727" y1="105.191" x2="404.049" y2="132.595" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint31_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(239.149 259.543) rotate(-41.9691) scale(249.995 96.3993)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint32_linear_129_11322" x1="86.5" y1="146.434" x2="217.578" y2="140.867" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint33_radial_129_11322" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(200.068 292.713) rotate(28.9342) scale(309.625 608.349)">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint34_linear_129_11322" x1="364.349" y1="-0.759149" x2="304.751" y2="48.01" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint35_linear_129_11322" x1="257.68" y1="12.1474" x2="279.317" y2="60.3572" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint36_linear_129_11322" x1="277.037" y1="12.1473" x2="326.765" y2="12.1473" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint37_linear_129_11322" x1="368.446" y1="49.7021" x2="345.064" y2="68.8359" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint38_linear_129_11322" x1="326.596" y1="54.7658" x2="335.085" y2="73.6801" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint39_linear_129_11322" x1="334.192" y1="54.7659" x2="353.702" y2="54.7659" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint40_linear_129_11322" x1="417.411" y1="10.5434" x2="407.454" y2="18.6913" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint41_linear_129_11322" x1="399.589" y1="12.6997" x2="403.204" y2="20.7541" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint42_linear_129_11322" x1="402.824" y1="12.6997" x2="411.132" y2="12.6997" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint43_linear_129_11322" x1="354.231" y1="156.258" x2="303.215" y2="198.005" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint44_linear_129_11322" x1="262.922" y1="167.306" x2="281.444" y2="208.574" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint45_linear_129_11322" x1="279.494" y1="167.306" x2="322.062" y2="167.306" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F02E65"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
@@ -1,20 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { onMount } from 'svelte';
|
||||
import { Breadcrumbs } from '.';
|
||||
import { Avatar, DropList, DropListItem, DropListLink } from '$lib/components';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { project } from '../../routes/console/[project]/store';
|
||||
import AppwriteLogo from '$lib/images/appwrite-gray-light.svg';
|
||||
import LightMode from '$lib/images/mode/light-mode.svg';
|
||||
import DarkMode from '$lib/images/mode/dark-mode.svg';
|
||||
import SystemMode from '$lib/images/mode/system-mode.svg';
|
||||
import { organizationList, organization, newOrgModal } from '$lib/stores/organization';
|
||||
|
||||
let showDropdown = false;
|
||||
|
||||
onMount(async () => {
|
||||
await organizationList.load();
|
||||
if (!$organization) {
|
||||
await organization.load($organizationList.teams[0].$id);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<a class="logo" href={`${base}/console`}>
|
||||
<a
|
||||
class="logo"
|
||||
href={$organization ? `${base}/console/organization-${$organization.$id}` : `${base}/console`}>
|
||||
<img src={AppwriteLogo} width="132" height="34" alt="Appwrite" />
|
||||
</a>
|
||||
|
||||
@@ -27,12 +37,18 @@
|
||||
href="https://appwrite.io/support"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="button is-small is-text"><span class="text">Support</span></a>
|
||||
class="button is-small is-text u-margin-inline-end-16"
|
||||
><span class="text">Support</span></a>
|
||||
<button class="button is-small is-secondary"><span class="text">Upgrade</span></button>
|
||||
</nav>
|
||||
<nav class="user-profile">
|
||||
{#if $user}
|
||||
<DropList bind:show={showDropdown} position="bottom" horizontal="left" arrow={false}>
|
||||
<DropList
|
||||
bind:show={showDropdown}
|
||||
position="bottom"
|
||||
horizontal="left"
|
||||
arrow={false}
|
||||
scrollable={true}>
|
||||
<button class="user-profile-button" on:click={() => (showDropdown = !showDropdown)}>
|
||||
<Avatar
|
||||
size={40}
|
||||
@@ -40,8 +56,8 @@
|
||||
src={sdkForConsole.avatars.getInitials($user.name, 40, 40).toString()} />
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
<span class="name">{$user.name}</span>
|
||||
{#if $project}
|
||||
<span class="title">{$project.name}</span>
|
||||
{#if $organization}
|
||||
<span class="title">{$organization.name}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span
|
||||
@@ -51,10 +67,28 @@
|
||||
class:icon-cheveron-down={!showDropdown} />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem icon="plus">New organisation</DropListItem>
|
||||
<DropListLink href="/console/$me">Your Account</DropListLink>
|
||||
{#if $organizationList?.total}
|
||||
{#each $organizationList.teams as org}
|
||||
<DropListLink
|
||||
href={`${base}/console/organization-${org.$id}`}
|
||||
on:click={() => {
|
||||
showDropdown = false;
|
||||
}}>{org.name}</DropListLink>
|
||||
{/each}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="other">
|
||||
<section class="drop-section">
|
||||
<ul class="drop-list">
|
||||
<DropListItem
|
||||
icon="plus"
|
||||
on:click={() => {
|
||||
showDropdown = false;
|
||||
newOrgModal.set(true);
|
||||
}}>New organization</DropListItem>
|
||||
<DropListLink href="/console/$me">Your Account</DropListLink>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="drop-section">
|
||||
<ul class="u-flex u-gap-12">
|
||||
<li>
|
||||
|
||||
@@ -7,3 +7,4 @@ export { default as Notification } from './notification.svelte';
|
||||
export { default as Notifications } from './notifications.svelte';
|
||||
export { default as Shell } from './shell.svelte';
|
||||
export { default as Breadcrumbs } from './breadcrumbs.svelte';
|
||||
export { default as Unauthenticated } from './unauthenticated.svelte';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/stores';
|
||||
|
||||
$: project = $page.params.project;
|
||||
$: path = `${base}/console/${project}`;
|
||||
$: path = `${base}/console/project-${project}`;
|
||||
</script>
|
||||
|
||||
<div class="side-nav">
|
||||
@@ -41,10 +41,12 @@
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/users`)}
|
||||
href={`${path}/users`}>
|
||||
class:is-selected={$page.url.pathname.startsWith(
|
||||
`${path}/authentication`
|
||||
)}
|
||||
href={`${path}/authentication`}>
|
||||
<span class="icon-user-group" aria-hidden="true" />
|
||||
<span class="text">Users</span>
|
||||
<span class="text">Authentication</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
@@ -56,24 +58,6 @@
|
||||
<span class="text">Functions</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/webhooks`)}
|
||||
href={`${path}/webhooks`}>
|
||||
<span class="icon-link" aria-hidden="true" />
|
||||
<span class="text">Webhooks</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="drop-list-item">
|
||||
<a
|
||||
class="drop-button"
|
||||
class:is-selected={$page.url.pathname.startsWith(`${path}/keys`)}
|
||||
href={`${path}/keys`}>
|
||||
<span class="icon-key" aria-hidden="true" />
|
||||
<span class="text">API Keys</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -6,37 +6,25 @@
|
||||
export let title: Notification['title'];
|
||||
export let buttons: Notification['buttons'];
|
||||
|
||||
let icon: string;
|
||||
switch (type) {
|
||||
case 'success':
|
||||
icon = 'check-circle';
|
||||
break;
|
||||
case 'warning':
|
||||
icon = 'exclamation';
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
icon = 'exclamation-circle';
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
icon = 'info';
|
||||
break;
|
||||
|
||||
default:
|
||||
icon = '';
|
||||
break;
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<li class={`alert-sticky is-${type}`}>
|
||||
<li
|
||||
class="alert-sticky"
|
||||
class:is-success={type === 'success'}
|
||||
class:is-warning={type === 'warning'}
|
||||
class:is-error={type === 'error'}
|
||||
class:is-info={type === 'info'}>
|
||||
<button class="x-button" aria-label="close alert" on:click={() => dispatch('dismiss')}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
<div class="alert-sticky-image">
|
||||
<span class={`icon-${icon}`} aria-hidden="true" />
|
||||
<span
|
||||
class:icon-check-circle={type === 'success'}
|
||||
class:icon-exclamation={type === 'warning'}
|
||||
class:icon-exclamation-circle={type === 'error'}
|
||||
class:icon-info={type === 'info'}
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
<div class="alert-sticky-content">
|
||||
{#if title}
|
||||
|
||||
+87
-15
@@ -1,18 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { navigating, page } from '$app/stores';
|
||||
import { tabs, title, backButton, copyData } from '$lib/stores/layout';
|
||||
import { tabs, title, backButton, copyData, titleDropdown } from '$lib/stores/layout';
|
||||
import { Cover } from '.';
|
||||
import { Copy } from '$lib/components';
|
||||
import { Copy, DropList, DropListItem, DropListLink, AvatarGroup } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
organization,
|
||||
memberList,
|
||||
newOrgModal,
|
||||
newMemberModal
|
||||
} from '$lib/stores/organization';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
export let isOpen = false;
|
||||
export let showSideNavigation = false;
|
||||
|
||||
$: base = `/console/${$page.params.project}`;
|
||||
|
||||
let tabsList: HTMLUListElement;
|
||||
let showLeft = false;
|
||||
let showRight = false;
|
||||
let showDropdown = false;
|
||||
|
||||
navigating.subscribe(() => {
|
||||
if (isOpen) isOpen = false;
|
||||
@@ -32,6 +40,20 @@
|
||||
}
|
||||
}, 10);
|
||||
};
|
||||
let avatars = [];
|
||||
let avatarsTotal = 0;
|
||||
|
||||
memberList.subscribe((value) => {
|
||||
if (value?.total > 0) {
|
||||
avatarsTotal = value.total;
|
||||
avatars = value.memberships.map((team) => {
|
||||
return {
|
||||
name: team.userName,
|
||||
img: sdkForConsole.avatars.getInitials(team.userName, 80, 80).toString()
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const onScroll = () => {
|
||||
if (!tabsList) {
|
||||
@@ -68,7 +90,7 @@
|
||||
class="icon-button is-no-desktop"
|
||||
aria-label="Open Menu"
|
||||
on:click={() => (isOpen = !isOpen)}>
|
||||
<span class={isOpen ? 'icon-x' : 'icon-menu'} aria-hidden="true" />
|
||||
<span class:icon-x={isOpen} class:icon-menu={!isOpen} aria-hidden="true" />
|
||||
</button>
|
||||
<slot name="header" />
|
||||
</header>
|
||||
@@ -83,15 +105,66 @@
|
||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
||||
</a>
|
||||
{/if}
|
||||
<h1 class="heading-level-4">
|
||||
<span class="text"> {$title}</span>
|
||||
</h1>
|
||||
{#if $titleDropdown?.length && $organization}
|
||||
<DropList
|
||||
bind:show={showDropdown}
|
||||
position="bottom"
|
||||
arrow={false}
|
||||
scrollable={true}>
|
||||
<button
|
||||
class="button is-text u-padding-inline-0"
|
||||
on:click={() => (showDropdown = !showDropdown)}>
|
||||
<h1 class="heading-level-4">
|
||||
<span class="text"> {$organization.name}</span>
|
||||
|
||||
<span
|
||||
class={`icon-cheveron-${showDropdown ? 'up' : 'down'}`}
|
||||
aria-hidden="true" />
|
||||
</h1>
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
{#each $titleDropdown as org}
|
||||
<DropListLink
|
||||
href={`${base}/console/organization-${org.$id}`}
|
||||
on:click={() => {
|
||||
showDropdown = false;
|
||||
}}>
|
||||
{org.name}
|
||||
</DropListLink>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="other">
|
||||
<section class="drop-section">
|
||||
<ul class="drop-list">
|
||||
<DropListItem
|
||||
icon="plus"
|
||||
on:click={() => {
|
||||
showDropdown = false;
|
||||
newOrgModal.set(true);
|
||||
}}>New Organization</DropListItem>
|
||||
</ul>
|
||||
</section></svelte:fragment>
|
||||
</DropList>
|
||||
<div class="u-margin-inline-start-auto">
|
||||
<div class="u-flex u-gap-16">
|
||||
<AvatarGroup size={40} {avatars} total={avatarsTotal} />
|
||||
<Button secondary on:click={() => newMemberModal.set(true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Invite</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<h1 class="heading-level-4">
|
||||
<span class="text"> {$title}</span>
|
||||
</h1>
|
||||
{/if}
|
||||
{#if $copyData?.value}
|
||||
<Copy value={$copyData.value}>
|
||||
<Pill button
|
||||
><span
|
||||
class="icon-duplicate"
|
||||
aria-hidden="true" />{$copyData.text}</Pill>
|
||||
<Pill button>
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
{$copyData.text}
|
||||
</Pill>
|
||||
</Copy>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
@@ -122,9 +195,8 @@
|
||||
<li class="tabs-item">
|
||||
<a
|
||||
class="tabs-button"
|
||||
href={`${base}/${tab.href}`}
|
||||
class:is-selected={$page.url.pathname ===
|
||||
`${base}/${tab.href}`}>
|
||||
href={tab.href}
|
||||
class:is-selected={$page.url.pathname === tab.href}>
|
||||
<span class="text">{tab.title}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import AppwriteLogo from '$lib/images/appwrite.svg';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
import LoginDark from '$lib/images/login/login-dark-mode.svg';
|
||||
import { app } from '$lib/stores/app';
|
||||
|
||||
const technologies = [
|
||||
'js',
|
||||
'flutter',
|
||||
'apple',
|
||||
'android',
|
||||
'node_js',
|
||||
'php',
|
||||
'python',
|
||||
'ruby',
|
||||
'dart',
|
||||
'kotlin',
|
||||
'swift'
|
||||
];
|
||||
</script>
|
||||
|
||||
<main class="grid-1-1 is-full-page" id="main">
|
||||
<section class="grid-1-1-col-1 u-flex u-flex-vertical">
|
||||
<div
|
||||
class="container u-margin-block-start-20"
|
||||
style="--p-container-max-size: var(--container-size-medium);">
|
||||
<a href="/">
|
||||
<img src={AppwriteLogo} width="196" height="47" class="u-block" alt="Appwrite" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="u-margin-block-start-auto" />
|
||||
|
||||
<div
|
||||
class="container u-margin-block-start-20"
|
||||
style="--p-container-max-size: var(--container-size-large);">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={LoginDark} alt="" class="u-only-dark" />
|
||||
{:else}
|
||||
<img src={LoginLight} alt="" class="u-only-light" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="u-margin-block-start-auto" />
|
||||
|
||||
<div
|
||||
class="container u-text-color-light-gray is-only-desktop"
|
||||
style="--p-container-max-size:var(--container-size-small); --p-container-padding-inline:1rem;">
|
||||
<p>Integrate with your favourite technologies</p>
|
||||
<ul
|
||||
class="u-flex u-main-center u-flex-wrap u-gap-16 u-margin-block-start-32 u-line-height-1 ">
|
||||
{#each technologies as tech}
|
||||
<li>
|
||||
<span
|
||||
class={`icon-${tech} u-font-size-32`}
|
||||
aria-hidden="true"
|
||||
aria-label={tech} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="u-margin-block-start-40" />
|
||||
</section>
|
||||
<section class="grid-1-1-col-2 u-flex u-main-center u-cross-center">
|
||||
<div class="container u-flex u-flex-vertical u-cross-center">
|
||||
<div class="u-margin-block-start-auto" />
|
||||
|
||||
<div class="u-max-width-500 u-width-full-line">
|
||||
<h1 class="heading-level-3 u-margin-block-start-auto"><slot name="title" /></h1>
|
||||
<div class="u-margin-block-start-40">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<ul class="inline-links is-center is-with-sep u-margin-block-start-32">
|
||||
<slot name="links" />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="u-margin-block-start-auto" />
|
||||
<p class="u-max-width-500 u-width-full-line u-margin-block-start-24">
|
||||
version 0.15.2.402
|
||||
</p>
|
||||
<div class="u-margin-block-start-20" />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
@@ -1,14 +1,25 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export type AuthMethod = {
|
||||
label: string;
|
||||
method: string;
|
||||
value: boolean | null;
|
||||
};
|
||||
|
||||
function createAuthMethods() {
|
||||
const { subscribe, set } = writable({
|
||||
list: [
|
||||
{
|
||||
label: 'Password',
|
||||
label: 'Email/Password',
|
||||
method: 'email-password',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
method: 'phone',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Magic URL',
|
||||
method: 'magic-url',
|
||||
@@ -28,11 +39,6 @@ function createAuthMethods() {
|
||||
label: 'JWT',
|
||||
method: 'jwt',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
method: 'phone',
|
||||
value: null
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -43,10 +49,15 @@ function createAuthMethods() {
|
||||
load: (project: Models.Project) => {
|
||||
const list = [
|
||||
{
|
||||
label: 'Password',
|
||||
label: 'Email/Password',
|
||||
method: 'email-password',
|
||||
value: project.authEmailPassword
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
method: 'phone',
|
||||
value: project.authPhone
|
||||
},
|
||||
{
|
||||
label: 'Magic URL',
|
||||
method: 'magic-url',
|
||||
@@ -66,11 +77,6 @@ function createAuthMethods() {
|
||||
label: 'JWT',
|
||||
method: 'jwt',
|
||||
value: project.authJWT
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
method: 'phone',
|
||||
value: project.authPhone
|
||||
}
|
||||
];
|
||||
set({ list });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { project } from '../../routes/console/[project]/store';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { project } from '../../routes/console/project-[project]/store';
|
||||
import { get, writable, readable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
|
||||
export type Tab = {
|
||||
href: string;
|
||||
@@ -14,6 +15,7 @@ export type Breadcrumb = {
|
||||
|
||||
export type updateLayoutArguments = {
|
||||
title: string;
|
||||
titleDropdown?: Models.Team[];
|
||||
level?: number;
|
||||
tabs?: Tab[];
|
||||
back?: string;
|
||||
@@ -22,6 +24,7 @@ export type updateLayoutArguments = {
|
||||
text: string;
|
||||
value: string;
|
||||
};
|
||||
customBase?: string;
|
||||
navigate?: {
|
||||
from: URL | null;
|
||||
to: URL;
|
||||
@@ -30,6 +33,7 @@ export type updateLayoutArguments = {
|
||||
|
||||
export const level = writable<number>();
|
||||
export const title = writable<string>('');
|
||||
export const titleDropdown = writable<Models.Team[]>([]);
|
||||
export const backButton = writable<string>('');
|
||||
export const tabs = writable<Tab[]>([]);
|
||||
export const breadcrumbs = writable<Map<number, Breadcrumb>>(new Map());
|
||||
@@ -40,7 +44,13 @@ export const copyData = writable({
|
||||
|
||||
export function updateLayout(args: updateLayoutArguments) {
|
||||
const projectId = get(project)?.$id;
|
||||
const base = projectId ? `/console/${projectId}` : `/console`;
|
||||
const base = args?.customBase ?? projectId ? `/console/project-${projectId}` : ``;
|
||||
|
||||
if (args?.tabs) {
|
||||
args.tabs.map((tab) => {
|
||||
tab.href = `${base}/${tab.href}`;
|
||||
});
|
||||
}
|
||||
|
||||
if (args?.navigate?.to) {
|
||||
const previousTabs = get(tabs);
|
||||
@@ -50,6 +60,7 @@ export function updateLayout(args: updateLayoutArguments) {
|
||||
}
|
||||
|
||||
title.set(args.title);
|
||||
titleDropdown.set(args.titleDropdown ?? []);
|
||||
backButton.set(args.back ?? null);
|
||||
copyData.set(args.copy ?? null);
|
||||
level.set(args.level ?? null);
|
||||
@@ -64,3 +75,5 @@ export function updateLayout(args: updateLayoutArguments) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const pageLimit = readable(12); // default page limit
|
||||
|
||||
@@ -32,7 +32,13 @@ export const addNotification = (notification: Omit<Notification, 'id'>) => {
|
||||
};
|
||||
|
||||
const n = { ...defaults, ...notification };
|
||||
notifications.update((all) => [n, ...all]);
|
||||
notifications.update((all) => {
|
||||
if (all.length < 3) {
|
||||
return [n, ...all];
|
||||
} else {
|
||||
return [n, ...all.slice(0, 2)];
|
||||
}
|
||||
});
|
||||
|
||||
if (n.timeout) setTimeout(() => dismissNotification(n.id), n.timeout);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import Apple from '../../routes/console/[project]/users/_appleOAuth.svelte';
|
||||
import Microsoft from '../../routes/console/[project]/users/_microsoftOAuth.svelte';
|
||||
import Okta from '../../routes/console/[project]/users/_oktaOAuth.svelte';
|
||||
import Auth0 from '../../routes/console/[project]/users/_auth0OAuth.svelte';
|
||||
import Main from '../../routes/console/[project]/users/_mainOAuth.svelte';
|
||||
import Apple from '../../routes/console/project-[project]/authentication/_appleOAuth.svelte';
|
||||
import Microsoft from '../../routes/console/project-[project]/authentication/_microsoftOAuth.svelte';
|
||||
import Okta from '../../routes/console/project-[project]/authentication/_oktaOAuth.svelte';
|
||||
import Auth0 from '../../routes/console/project-[project]/authentication/_auth0OAuth.svelte';
|
||||
import Main from '../../routes/console/project-[project]/authentication/_mainOAuth.svelte';
|
||||
|
||||
export type Provider = {
|
||||
name: string;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/env';
|
||||
import { get } from 'svelte/store';
|
||||
import { base } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
function createOrganizationList() {
|
||||
const { subscribe, set } = writable<Models.TeamList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('organizationList')) : null
|
||||
);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async () => {
|
||||
const response = await sdkForConsole.teams.list();
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
function createOrganization() {
|
||||
const { subscribe, set } = writable<Models.Team>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('organization')) : null
|
||||
);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (teamId: string) => {
|
||||
const response = await sdkForConsole.teams.get(teamId);
|
||||
set(response);
|
||||
},
|
||||
deleteCache: () => {
|
||||
sessionStorage.removeItem('organization');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createProjectList() {
|
||||
const { subscribe, set } = writable<Models.ProjectList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('projectList')) : null
|
||||
);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (search: string, limit: number, offset: number) => {
|
||||
const response = await sdkForConsole.projects.list(
|
||||
search,
|
||||
limit,
|
||||
offset,
|
||||
undefined,
|
||||
undefined,
|
||||
'ASC'
|
||||
);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createMemberList() {
|
||||
const { subscribe, set } = writable<Models.MembershipList>(
|
||||
browser ? JSON.parse(sessionStorage.getItem('memberList')) : null
|
||||
);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set,
|
||||
load: async (teamId: string, search: string, limit: number, offset: number) => {
|
||||
const response = await sdkForConsole.teams.getMemberships(
|
||||
teamId,
|
||||
search,
|
||||
limit,
|
||||
offset
|
||||
);
|
||||
set(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const organizationList = createOrganizationList();
|
||||
export const organization = createOrganization();
|
||||
export const projectList = createProjectList();
|
||||
export const memberList = createMemberList();
|
||||
export const newOrgModal = writable<boolean>(false);
|
||||
export const newMemberModal = writable<boolean>(false);
|
||||
|
||||
if (browser) {
|
||||
organizationList.subscribe((n) =>
|
||||
sessionStorage?.setItem('organizationList', JSON.stringify(n ?? ''))
|
||||
);
|
||||
organization.subscribe((n) => sessionStorage?.setItem('organization', JSON.stringify(n ?? '')));
|
||||
projectList.subscribe((n) => sessionStorage?.setItem('projectList', JSON.stringify(n ?? '')));
|
||||
memberList.subscribe((n) => sessionStorage?.setItem('memberList', JSON.stringify(n ?? '')));
|
||||
}
|
||||
|
||||
export const redirectTo = async () => {
|
||||
let org = get(organization);
|
||||
if (org) {
|
||||
await goto(`${base}/console/organization-${org.$id}`);
|
||||
} else {
|
||||
await organizationList.load();
|
||||
const orgList = get(organizationList);
|
||||
if (orgList?.total) {
|
||||
await organization.load(orgList.teams[0].$id);
|
||||
org = get(organization);
|
||||
await goto(`${base}/console/organization-${org.$id}`);
|
||||
} else {
|
||||
newOrgModal.set(true);
|
||||
await goto(`${base}/console`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -7,16 +7,19 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { redirectTo } from '$lib/stores/organization';
|
||||
import { onMount } from 'svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { browser } from '$app/env';
|
||||
import { app } from '$lib/stores/app';
|
||||
import Notifications from '$lib/layout/notifications.svelte';
|
||||
import Loading from './_loading.svelte';
|
||||
import { webVitals } from '$lib/helpers/vitals';
|
||||
|
||||
let loaded = false;
|
||||
|
||||
if (browser) {
|
||||
window.VERCEL_ANALYTICS_ID = import.meta.env.VERCEL_ANALYTICS_ID?.toString() ?? false;
|
||||
window.GOOGLE_ANALYTICS = import.meta.env.VITE_GOOGLE_ANALYTICS?.toString() ?? false;
|
||||
}
|
||||
|
||||
@@ -27,7 +30,7 @@
|
||||
}
|
||||
|
||||
if (!$page.url.pathname.startsWith('/console')) {
|
||||
await goto(`${base}/console`);
|
||||
await redirectTo();
|
||||
}
|
||||
} catch (error) {
|
||||
await goto(`${base}/login`);
|
||||
@@ -36,6 +39,14 @@
|
||||
}
|
||||
});
|
||||
|
||||
$: if (browser && window.VERCEL_ANALYTICS_ID) {
|
||||
webVitals({
|
||||
path: $page.url.pathname,
|
||||
params: $page.params,
|
||||
analyticsId: window.VERCEL_ANALYTICS_ID
|
||||
});
|
||||
}
|
||||
|
||||
$: {
|
||||
if (browser) {
|
||||
if ($app.theme === 'auto') {
|
||||
|
||||
@@ -1,469 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Alert, CardGrid, Box } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import {
|
||||
Button,
|
||||
InputText,
|
||||
InputTags,
|
||||
InputNumber,
|
||||
InputSelect,
|
||||
InputSwitch,
|
||||
Helper
|
||||
} from '$lib/elements/forms';
|
||||
import { bucket } from './store';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sizeToBytes } from '$lib/helpers/sizeConvertion';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import Delete from './_deleteBucket.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'size' = false,
|
||||
errorMessage = 'Something went wrong',
|
||||
errorType: 'error' | 'warning' | 'success' = 'error';
|
||||
let enabled: boolean = null,
|
||||
bucketName: string = null,
|
||||
bucketPermissions: string = null,
|
||||
bucketRead: string[] = null,
|
||||
bucketWrite: string[] = null,
|
||||
arePermsDisabled = true,
|
||||
encryption: boolean = null,
|
||||
antivirus: boolean = null,
|
||||
maxSize: number;
|
||||
let byteUnit: 'Bytes' | 'KB' | 'MB' | 'GB' = 'Bytes',
|
||||
options = [
|
||||
{ label: 'Bytes', value: 'Bytes' },
|
||||
{ label: 'Kilobytes', value: 'KB' },
|
||||
{ label: 'Megabytes', value: 'MB' },
|
||||
{ label: 'Gigabytes', value: 'GB' }
|
||||
];
|
||||
let extensions = $bucket.allowedFileExtensions;
|
||||
let isExtensionsDisabled = true;
|
||||
|
||||
onMount(async () => {
|
||||
await bucket.load($page.params.bucket);
|
||||
enabled ??= $bucket.enabled;
|
||||
bucketName ??= $bucket.name;
|
||||
bucketName ??= $bucket.name;
|
||||
bucketPermissions ??= $bucket.permission;
|
||||
bucketRead ??= $bucket.$read;
|
||||
bucketWrite ??= $bucket.$write;
|
||||
encryption ??= $bucket.encryption;
|
||||
antivirus ??= $bucket.antivirus;
|
||||
});
|
||||
|
||||
$: if (bucketPermissions || bucketRead || bucketWrite) {
|
||||
if (bucketPermissions !== $bucket.permission) {
|
||||
arePermsDisabled = false;
|
||||
} else if (bucketRead) {
|
||||
if (JSON.stringify(bucketRead) !== JSON.stringify($bucket.$read)) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
} else if (bucketWrite) {
|
||||
if (JSON.stringify(bucketWrite) !== JSON.stringify($bucket.$write)) {
|
||||
arePermsDisabled = false;
|
||||
} else arePermsDisabled = true;
|
||||
}
|
||||
}
|
||||
$: if (extensions) {
|
||||
if (JSON.stringify(extensions) !== JSON.stringify($bucket.allowedFileExtensions)) {
|
||||
isExtensionsDisabled = false;
|
||||
} else isExtensionsDisabled = true;
|
||||
}
|
||||
|
||||
function addError(location: typeof showError, message: string, type: typeof errorType) {
|
||||
showError = location;
|
||||
errorMessage = message;
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
async function toggleBucket() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
$bucket.permission,
|
||||
$bucket.$read,
|
||||
$bucket.$write,
|
||||
enabled
|
||||
);
|
||||
$bucket.enabled = enabled;
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updated`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket($bucket.$id, $bucket.name, $bucket.permission);
|
||||
$bucket.name = bucketName;
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addError('name', error.message, 'error');
|
||||
}
|
||||
}
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
bucketPermissions,
|
||||
bucketPermissions === 'bucket' ? bucketRead : $bucket.$read,
|
||||
bucketPermissions === 'bucket' ? bucketWrite : $bucket.$write
|
||||
);
|
||||
$bucket.permission = bucketPermissions;
|
||||
$bucket.$read = bucketRead;
|
||||
$bucket.$write = bucketWrite;
|
||||
arePermsDisabled = true;
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSecurity() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
$bucket.permission,
|
||||
$bucket.$read,
|
||||
$bucket.$write,
|
||||
$bucket.enabled,
|
||||
$bucket.maximumFileSize,
|
||||
$bucket.allowedFileExtensions,
|
||||
encryption,
|
||||
antivirus
|
||||
);
|
||||
$bucket.encryption = encryption;
|
||||
$bucket.antivirus = antivirus;
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updatede`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateMaxSize() {
|
||||
let size = sizeToBytes(maxSize, byteUnit);
|
||||
console.log(size);
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
$bucket.permission,
|
||||
$bucket.$read,
|
||||
$bucket.$write,
|
||||
$bucket.enabled,
|
||||
size
|
||||
);
|
||||
$bucket.maximumFileSize = maxSize;
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updatede`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAllowedExtensions() {
|
||||
try {
|
||||
await sdkForProject.storage.updateBucket(
|
||||
$bucket.$id,
|
||||
$bucket.name,
|
||||
$bucket.permission,
|
||||
$bucket.$read,
|
||||
$bucket.$write,
|
||||
$bucket.enabled,
|
||||
$bucket.maximumFileSize,
|
||||
extensions
|
||||
);
|
||||
$bucket.allowedFileExtensions = extensions;
|
||||
addNotification({
|
||||
message: `${$bucket.name} has been updatede`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if $bucket}
|
||||
<CardGrid>
|
||||
<h2 class="heading-level-7">{$bucket.name}</h2>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputSwitch
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
id="toggle"
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<p>Created: {toLocaleDateTime($bucket.$createdAt)}</p>
|
||||
<p>Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={enabled === $bucket.enabled}
|
||||
on:click={() => {
|
||||
toggleBucket();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={bucketName} />
|
||||
{#if showError === 'name'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={bucketName === $bucket.name || !bucketName}
|
||||
on:click={() => {
|
||||
updateName();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Permissions</h6>
|
||||
<p>
|
||||
Assign read or write permissions at the <b>Bucket Level</b> or
|
||||
<b>File Level</b>. If Bucket Level permissions are assigned, file permissions will
|
||||
be ignored.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="u-flex u-gap-12 common-section">
|
||||
<li>
|
||||
<label class="label">
|
||||
<input
|
||||
type="radio"
|
||||
class="is-small"
|
||||
name="level"
|
||||
bind:group={bucketPermissions}
|
||||
value="bucket" />
|
||||
<span>Bucket Level</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="label">
|
||||
<input
|
||||
type="radio"
|
||||
class="is-small"
|
||||
name="level"
|
||||
bind:group={bucketPermissions}
|
||||
value="file" />
|
||||
<span>File Level</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<Alert type="info">
|
||||
<p>
|
||||
Tip: Add <b>role:all</b> for wildcards access. Check out our documentation
|
||||
for more on <a href="/#">Permissions</a>
|
||||
</p>
|
||||
</Alert>
|
||||
{#if bucketPermissions === 'bucket'}
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="read"
|
||||
label="Read Access"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={bucketRead} />
|
||||
<InputTags
|
||||
id="write"
|
||||
label="Write Access"
|
||||
placeholder="User ID, Team ID, or Role"
|
||||
bind:tags={bucketWrite} />
|
||||
</ul>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={arePermsDisabled}
|
||||
on:click={() => {
|
||||
updatePermissions();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h2 class="heading-level-7">Update Security Settings</h2>
|
||||
<p>
|
||||
Enable or disable security services for the bucket including <b> Ecryption</b>
|
||||
and <b> Antivirus scanning.</b>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="form-list">
|
||||
<li class="form-item">
|
||||
<label class="choice-item" for="encryption">
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
label="Encryption"
|
||||
id="encryption"
|
||||
type="checkbox"
|
||||
class="switch"
|
||||
role="switch"
|
||||
bind:checked={encryption} />
|
||||
</div>
|
||||
<div class="choice-item-content">
|
||||
<div class="choice-item-title">Encryption</div>
|
||||
|
||||
<div class="choice-item-paragraph">
|
||||
This parameter allows you to configure whether or not the files
|
||||
inside the bucket will be encrypted. We don't encrypt files
|
||||
bigger than 20MB.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
<li class="form-item">
|
||||
<label class="choice-item" for="antivirus">
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
label="Antivirus"
|
||||
id="antivirus"
|
||||
type="checkbox"
|
||||
class="switch"
|
||||
role="switch"
|
||||
bind:checked={antivirus} />
|
||||
</div>
|
||||
<div class="choice-item-content">
|
||||
<div class="choice-item-title">Antivirus</div>
|
||||
|
||||
<div class="choice-item-paragraph">
|
||||
This parameter allows you to configure whether or not the files
|
||||
inside the bucket should be scanned by the Appwrite Antivirus
|
||||
scanner.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<li />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={encryption === $bucket.encryption && antivirus === $bucket.antivirus}
|
||||
on:click={() => {
|
||||
updateSecurity();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h2 class="heading-level-6">Update Maximum File Size</h2>
|
||||
<p>Set the maximum file size allowed in the bucket.</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul class="u-flex u-gap-12">
|
||||
<InputNumber
|
||||
id="size"
|
||||
label="Size"
|
||||
placeholder={$bucket.maximumFileSize + ''}
|
||||
bind:value={maxSize} />
|
||||
<InputSelect id="bytes" label="Bytes" {options} bind:value={byteUnit} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={!maxSize}
|
||||
on:click={() => {
|
||||
updateMaxSize();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Allowed File Extensions</h6>
|
||||
<p>
|
||||
A maxiumum of 100 file extensions can be added. Leave blank to allow all file types.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Alert type="info">
|
||||
<p>
|
||||
Tip: Commonly added extensions include JPG, PNG, SVG, GIF, HTML, PDF, MP4.
|
||||
</p>
|
||||
</Alert>
|
||||
<ul class="common-section">
|
||||
<InputTags
|
||||
id="read"
|
||||
label="Allowed file extensions"
|
||||
placeholder="Allowed file extensions (mp4, jpg, pdf, etc.)"
|
||||
bind:tags={extensions} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={isExtensionsDisabled}
|
||||
on:click={() => {
|
||||
updateAllowedExtensions();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Delete Bucket</h6>
|
||||
<p>
|
||||
The bucket will be permanently deleted, including all the files within it. This
|
||||
action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold">{$bucket.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>Last Updated: {toLocaleDateTime($bucket.$updatedAt)}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
@@ -1,468 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Avatar, CardGrid, Box, DropList, DropListItem } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import {
|
||||
Button,
|
||||
InputText,
|
||||
InputEmail,
|
||||
InputPassword,
|
||||
InputPhone,
|
||||
Helper
|
||||
} from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import DeleteUser from './_deleteUser.svelte';
|
||||
import { user } from './store';
|
||||
import { onMount } from 'svelte';
|
||||
import { title } from '$lib/stores/layout';
|
||||
|
||||
$: if (prefs) {
|
||||
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($user.prefs))) {
|
||||
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
|
||||
arePrefsDisabled = false;
|
||||
}
|
||||
} else {
|
||||
arePrefsDisabled = true;
|
||||
}
|
||||
}
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'email' | 'phone' | 'password' = false;
|
||||
let errorMessage = 'Something went wrong';
|
||||
let errorType: 'error' | 'warning' | 'success' = 'error';
|
||||
let userName: string = null,
|
||||
userEmail: string = null,
|
||||
userPhone: string = null,
|
||||
newPassword: string = null;
|
||||
let prefs: [string, unknown][] = null;
|
||||
let arePrefsDisabled = true;
|
||||
let showVerifcationDropdown = false;
|
||||
|
||||
onMount(async () => {
|
||||
await user.load($page.params.user);
|
||||
prefs = Object.entries($user.prefs);
|
||||
if (!prefs?.length) {
|
||||
prefs.push(['', '']);
|
||||
}
|
||||
userName ??= $user.name;
|
||||
userEmail ??= $user.email;
|
||||
userPhone ??= $user.phone;
|
||||
});
|
||||
|
||||
const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 48, 48).toString();
|
||||
|
||||
function addError(location: typeof showError, message: string, type: typeof errorType) {
|
||||
showError = location;
|
||||
errorMessage = message;
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
async function updateVerificationEmail() {
|
||||
try {
|
||||
await sdkForProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
|
||||
$user.emailVerification = !$user.emailVerification;
|
||||
addNotification({
|
||||
message: `The account has been ${
|
||||
$user.emailVerification ? 'verified' : 'unverified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateVerificationPhone() {
|
||||
try {
|
||||
await sdkForProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
|
||||
$user.phoneVerification = !$user.phoneVerification;
|
||||
addNotification({
|
||||
message: `The account has been ${
|
||||
$user.phoneVerification ? 'verified' : 'unverified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateStatus() {
|
||||
try {
|
||||
await sdkForProject.users.updateStatus($user.$id, !$user.status);
|
||||
$user.status = !$user.status;
|
||||
addNotification({
|
||||
message: `The account has been ${$user.status ? 'unblocked' : 'blocked'}`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.users.updateName($user.$id, userName);
|
||||
$user.name = userName;
|
||||
title.set(userName);
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addError('name', error.message, 'error');
|
||||
}
|
||||
}
|
||||
async function updateEmail() {
|
||||
try {
|
||||
await sdkForProject.users.updateEmail($user.$id, userEmail);
|
||||
$user.email = userEmail;
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Email has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addError('email', error.message, 'error');
|
||||
}
|
||||
}
|
||||
async function updatePhone() {
|
||||
try {
|
||||
await sdkForProject.users.updatePhone($user.$id, userPhone);
|
||||
$user.phone = userPhone;
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Phone has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addError('phone', error.message, 'error');
|
||||
}
|
||||
}
|
||||
async function updatePassword() {
|
||||
try {
|
||||
await sdkForProject.users.updatePassword($user.$id, newPassword);
|
||||
newPassword = null;
|
||||
showError = false;
|
||||
addNotification({
|
||||
message: 'Password has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addError('password', error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePrefs() {
|
||||
try {
|
||||
let updatedPrefs = Object.fromEntries(prefs);
|
||||
|
||||
await sdkForProject.users.updatePrefs($user.$id, updatedPrefs);
|
||||
$user.prefs = updatedPrefs;
|
||||
arePrefsDisabled = true;
|
||||
|
||||
addNotification({
|
||||
message: 'Preferences have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $user}
|
||||
<Container>
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
<Avatar size={48} name={$user.name} src={getAvatar($user.name)} />
|
||||
<h6 class="heading-level-7">{$user.name}</h6>
|
||||
{#if !$user.status}
|
||||
<Pill danger>blocked</Pill>
|
||||
{:else if $user.email && $user.phone}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification && $user.phoneVerification
|
||||
? 'verified'
|
||||
: $user.emailVerification
|
||||
? 'verified email'
|
||||
: $user.phoneVerification
|
||||
? 'verified phone'
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{:else}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification
|
||||
? 'verified '
|
||||
: $user.phoneVerification
|
||||
? 'verified '
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div>
|
||||
{#if $user.email}
|
||||
<p class="title">{$user.email}</p>
|
||||
{/if}
|
||||
{#if $user.phone}
|
||||
<p class="title">{$user.phone}</p>
|
||||
{/if}
|
||||
<p>Joined: {toLocaleDateTime($user.registration)}</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
text={$user.status}
|
||||
secondary={!$user.status}
|
||||
on:click={() => updateStatus()}
|
||||
>{$user.status ? 'Block account' : 'Unblock account'}</Button>
|
||||
{#if $user.status}
|
||||
{#if $user.phone && $user.email}
|
||||
<DropList
|
||||
bind:show={showVerifcationDropdown}
|
||||
position="top"
|
||||
horizontal="left"
|
||||
arrow={true}
|
||||
arrowPosition="end">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() =>
|
||||
(showVerifcationDropdown = !showVerifcationDropdown)}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem icon="mail" on:click={() => updateVerificationEmail()}
|
||||
>{$user.emailVerification ? 'Unverify' : 'Verify'} email</DropListItem>
|
||||
<DropListItem
|
||||
icon="phone"
|
||||
on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} phone</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{:else if !$user.phone}
|
||||
<Button secondary on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
{:else if !$user.email}
|
||||
<Button secondary on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={userName} />
|
||||
{#if showError === 'name'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={userName === $user.name || !userName}
|
||||
on:click={() => {
|
||||
updateName();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Email</h6>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autocomplete={false}
|
||||
bind:value={userEmail} />
|
||||
{#if showError === 'email'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={userEmail === $user.email || !userEmail}
|
||||
on:click={() => {
|
||||
updateEmail();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Phone</h6>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPhone
|
||||
id="phone"
|
||||
label="Phone"
|
||||
placeholder="Enter phone number"
|
||||
autocomplete={false}
|
||||
bind:value={userPhone} />
|
||||
{#if showError === 'phone'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={userPhone === $user.phone || !userPhone}
|
||||
on:click={() => {
|
||||
updatePhone();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Update Password</h6>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Enter a new password. A password must contain <b> at least 8 characters.</b>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPassword
|
||||
id="newPassword"
|
||||
label="New Password"
|
||||
placeholder="Enter new password"
|
||||
autocomplete={false}
|
||||
meter={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={newPassword} />
|
||||
{#if showError === 'password'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={!newPassword}
|
||||
on:click={() => {
|
||||
updatePassword();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">User Preferences</h6>
|
||||
<p>
|
||||
You can update your user preferences by storing information on the user's objects so
|
||||
they can easily be shared across devices and sessions.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list">
|
||||
{#if prefs}
|
||||
{#each prefs as [key, value], index}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`key-${index}`}>Key</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`key-${key}`}
|
||||
placeholder="Enter key"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value={key} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`value-${index}`}>Value</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`value-${value}`}
|
||||
placeholder="Enter value"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
disabled={(!key || !value) && index === 0}
|
||||
on:click={() => {
|
||||
if (index === 0) {
|
||||
prefs = [['', '']];
|
||||
} else {
|
||||
prefs.splice(index, 1);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
<Button
|
||||
text
|
||||
on:click={() => {
|
||||
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
|
||||
prefs.push(['', '']);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text"> Add Preference </span></Button>
|
||||
</form>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePrefsDisabled} on:click={() => updatePrefs()}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Danger Zone</h6>
|
||||
</div>
|
||||
<p>
|
||||
The user will be permanently deleted, including all data associated with this user.
|
||||
This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
<Avatar size={48} name={$user.name} src={getAvatar($user.name)} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold">{$user.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>{$user.email}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Container>
|
||||
{/if}
|
||||
<DeleteUser bind:showDelete />
|
||||
@@ -1,113 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Empty, Pagination } from '$lib/components';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCellText
|
||||
} from '$lib/elements/table';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import DeleteAllSessions from './_deleteAllSessions.svelte';
|
||||
import DeleteSessions from './_deleteSession.svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { app } from '$lib/stores/app';
|
||||
|
||||
let offset = 0;
|
||||
let showDelete = false;
|
||||
let showDeleteAll = false;
|
||||
let selectedSessionId: string = null;
|
||||
|
||||
const limit = 5;
|
||||
const request = sdkForProject.users.getSessions($page.params.user);
|
||||
|
||||
//TODO: replace session.expire with "last activity"
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#await request}
|
||||
<div aria-busy="true" />
|
||||
{:then response}
|
||||
{#if response.total}
|
||||
<div class="u-flex u-main-end common-section">
|
||||
<Button secondary on:click={() => (showDeleteAll = true)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Delete All</span>
|
||||
</Button>
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Browser</TableCellHead>
|
||||
<TableCellHead>Device</TableCellHead>
|
||||
<TableCellHead>IP</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each response.sessions as session}
|
||||
<TableRow>
|
||||
<TableCellText title="Client">
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="image-item">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={`/icons/${
|
||||
$app.themeInUse
|
||||
}/color/${session?.clientName.toLocaleLowerCase()}.svg`}
|
||||
alt={session.clientName} />
|
||||
</div>
|
||||
<span class="u-line-height-1-5">
|
||||
<p>{session.clientName}</p>
|
||||
<span class="u-small">
|
||||
Last activity: {toLocaleDateTime(session?.expire)}
|
||||
</span></span>
|
||||
</div>
|
||||
</TableCellText>
|
||||
|
||||
<TableCellText title="IP">{session.ip}</TableCellText>
|
||||
<TableCellText title="">
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
aria-label="Delete item"
|
||||
on:click={() => {
|
||||
selectedSessionId = session.$id;
|
||||
showDelete = true;
|
||||
}}>
|
||||
<span class="icon-trash" aria-hidden="true" />
|
||||
</button>
|
||||
</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{:else}
|
||||
<Empty centered>
|
||||
<div class="u-flex u-flex-vertical u-cross-center">
|
||||
<div class="common-section">
|
||||
<p>No session available</p>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/users?sdk=nodejs-default#usersGetSessions"
|
||||
>Documentation</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
{/if}
|
||||
<div
|
||||
class="u-flex u-margin-block-start-32
|
||||
u-main-space-between">
|
||||
<p class="text">Total results: {response.total}</p>
|
||||
<Pagination {limit} bind:offset sum={response.total} />
|
||||
</div>
|
||||
{/await}
|
||||
</Container>
|
||||
|
||||
<DeleteSessions {selectedSessionId} bind:showDelete />
|
||||
<DeleteAllSessions bind:showDeleteAll />
|
||||
@@ -1,18 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Shell from '$lib/layout/shell.svelte';
|
||||
import Header from '$lib/layout/header.svelte';
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
|
||||
updateLayout({
|
||||
title: 'Projects',
|
||||
level: 0
|
||||
});
|
||||
</script>
|
||||
|
||||
<Shell>
|
||||
<svelte:fragment slot="header">
|
||||
<Header />
|
||||
</svelte:fragment>
|
||||
<slot />
|
||||
<footer class="main-footer" />
|
||||
</Shell>
|
||||
@@ -3,14 +3,40 @@
|
||||
import SideNavigation from '$lib/layout/navigation.svelte';
|
||||
import Header from '$lib/layout/header.svelte';
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
import {
|
||||
organization,
|
||||
organizationList,
|
||||
newOrgModal,
|
||||
redirectTo
|
||||
} from '$lib/stores/organization';
|
||||
import Create from './_createOrganization.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
updateLayout({
|
||||
title: 'Projects',
|
||||
title: $organization?.name ?? '',
|
||||
level: 0
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
if ($page.url.pathname === '/console' && !$newOrgModal) {
|
||||
await redirectTo();
|
||||
}
|
||||
});
|
||||
|
||||
afterNavigate(async () => {
|
||||
if ($page.url.pathname === '/console' && !$newOrgModal) {
|
||||
await redirectTo();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Shell showSideNavigation>
|
||||
<svelte:head>
|
||||
<title>Appwrite - Console</title>
|
||||
</svelte:head>
|
||||
|
||||
<Shell showSideNavigation={$page.url.pathname !== '/console' && !$page?.params.organization}>
|
||||
<svelte:fragment slot="header">
|
||||
<Header />
|
||||
</svelte:fragment>
|
||||
@@ -20,3 +46,5 @@
|
||||
<slot />
|
||||
<footer class="main-footer" />
|
||||
</Shell>
|
||||
|
||||
<Create bind:show={$newOrgModal} closable={!!$organizationList?.total} />
|
||||
|
||||
@@ -1,37 +1,96 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { InputText, Button, Form } from '$lib/elements/forms';
|
||||
import { Modal, InnerModal } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputText, Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { organization, organizationList } from '$lib/stores/organization';
|
||||
import { titleDropdown } from '$lib/stores/layout';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let show = false;
|
||||
|
||||
export let closable = true;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let name: string;
|
||||
let id: string;
|
||||
let showDropdown = false;
|
||||
let error: string;
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.create('unique()', name);
|
||||
name = null;
|
||||
const team = await sdkForConsole.teams.create(id ?? 'unique()', name);
|
||||
dispatch('created');
|
||||
} catch (error) {
|
||||
await organizationList.load();
|
||||
titleDropdown.set($organizationList.teams);
|
||||
organization.set(team);
|
||||
await goto(`/console/organization-${$organization.$id}`);
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
type: 'success',
|
||||
message: `${name} has been created`
|
||||
});
|
||||
name = null;
|
||||
id = null;
|
||||
show = false;
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal bind:show>
|
||||
<svelte:fragment slot="header">Create Project</svelte:fragment>
|
||||
<InputText id="name" label="Name" bind:value={name} required />
|
||||
<Modal {error} size="big" bind:show {closable}>
|
||||
<svelte:fragment slot="header">Create New Organization</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
bind:value={name}
|
||||
autofocus={true}
|
||||
required />
|
||||
{#if !showDropdown}
|
||||
<div>
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
Organization ID
|
||||
</span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<InnerModal bind:show={showDropdown}>
|
||||
<svelte:fragment slot="title">Organization ID</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Enter a custom organization ID. Leave blank for a randomly generated one.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="form">
|
||||
<InputText
|
||||
id="id"
|
||||
label="Custom ID"
|
||||
showLabel={false}
|
||||
placeholder="Enter ID"
|
||||
autofocus={true}
|
||||
bind:value={id} />
|
||||
|
||||
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading
|
||||
underscore, period
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</InnerModal>
|
||||
{/if}
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button disabled={!closable} secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit>Create</Button>
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { InputText, Button, InputCustomId, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
export let teamId: string;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let id: string;
|
||||
let name: string;
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
const project = await sdkForConsole.projects.create(id, name, teamId);
|
||||
id = name = null;
|
||||
dispatch('created', project);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal bind:show>
|
||||
<svelte:fragment slot="header">Create Project</svelte:fragment>
|
||||
<InputCustomId id="id" label="ID" bind:value={id} required />
|
||||
<InputText id="name" label="Name" bind:value={name} required />
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit>Create</Button>
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -1,67 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Tiles, Tile } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import CreateOrganization from './_createOrganization.svelte';
|
||||
import CreateProject from './_createProject.svelte';
|
||||
|
||||
type Organization = Models.Team & {
|
||||
projects: Models.Project[];
|
||||
};
|
||||
|
||||
const request: Promise<Organization[]> = Promise.all([
|
||||
sdkForConsole.projects.list(),
|
||||
sdkForConsole.teams.list()
|
||||
]).then(([{ projects }, { teams }]) => {
|
||||
return teams.map((team) => {
|
||||
return {
|
||||
...team,
|
||||
projects: projects.filter((a) => a.teamId === team.$id)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
let addProject = false;
|
||||
let addOrganization = false;
|
||||
let currentOrganization = '';
|
||||
|
||||
const createProject = (teamId: string) => {
|
||||
currentOrganization = teamId;
|
||||
addProject = true;
|
||||
};
|
||||
const projectCreated = async (event: CustomEvent<Models.Project>) => {
|
||||
await goto(`${base}/console/${event.detail.$id}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Appwrite - Console</title>
|
||||
</svelte:head>
|
||||
<Container>
|
||||
<h1>Your Organizations</h1>
|
||||
<Button on:click={() => (addOrganization = true)}>Add Organization</Button>
|
||||
|
||||
{#await request}
|
||||
<div aria-busy="true" />
|
||||
{:then organizations}
|
||||
{#each organizations as organization}
|
||||
<h1>{organization.name}</h1>
|
||||
<Tiles>
|
||||
{#each organization.projects as project}
|
||||
<Tile href={`${base}/console/${project.$id}`} title={project.name} />
|
||||
{/each}
|
||||
</Tiles>
|
||||
<Button on:click={() => createProject(organization.$id)}>Create Project</Button>
|
||||
{/each}
|
||||
{/await}
|
||||
</Container>
|
||||
|
||||
<CreateOrganization bind:show={addOrganization} />
|
||||
<CreateProject
|
||||
bind:show={addProject}
|
||||
bind:teamId={currentOrganization}
|
||||
on:created={projectCreated} />
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import {
|
||||
organizationList,
|
||||
organization,
|
||||
memberList,
|
||||
newOrgModal,
|
||||
newMemberModal
|
||||
} from '$lib/stores/organization';
|
||||
import CreateMember from './_createMember.svelte';
|
||||
import Create from '../_createOrganization.svelte';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
$: organizationId = $page.params.organization;
|
||||
$: path = `console/organization-${organizationId}`;
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
|
||||
async function handle(event = null) {
|
||||
if ($organization.$id !== organizationId) {
|
||||
await organization.load(organizationId);
|
||||
await memberList.load(organizationId, '', 12, 0);
|
||||
}
|
||||
updateLayout({
|
||||
navigate: event,
|
||||
title: $organization?.name,
|
||||
titleDropdown: $organizationList.teams,
|
||||
level: 0,
|
||||
customBase: '',
|
||||
breadcrumbs: {
|
||||
title: `${$organization.name}`,
|
||||
href: path
|
||||
},
|
||||
tabs: [
|
||||
{
|
||||
href: path,
|
||||
title: 'Projects'
|
||||
},
|
||||
{
|
||||
href: `${path}/members`,
|
||||
title: 'Members'
|
||||
},
|
||||
{
|
||||
href: `${path}/settings`,
|
||||
title: 'Settings'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Appwrite - Organizations</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if $organization}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
<Create bind:show={$newOrgModal} />
|
||||
<CreateMember bind:showCreate={$newMemberModal} />
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import { Modal } from '$lib/components';
|
||||
import { InputText, InputEmail, Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { organization, memberList } from '$lib/stores/organization';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
|
||||
export let showCreate = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let email: string, name: string, error: string;
|
||||
const url = `${$page.url.origin}/console/`;
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
const team = await sdkForConsole.teams.createMembership(
|
||||
$organization.$id,
|
||||
email,
|
||||
[],
|
||||
url,
|
||||
name
|
||||
);
|
||||
showCreate = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Invite has been sent to ${email}`
|
||||
});
|
||||
memberList.load($organization.$id, '', $pageLimit, 0);
|
||||
dispatch('created', team);
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
|
||||
$: if (!showCreate) {
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal {error} size="big" bind:show={showCreate}>
|
||||
<svelte:fragment slot="header">Invite Member</svelte:fragment>
|
||||
<FormList>
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autofocus={true}
|
||||
bind:value={email} />
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name (optional)"
|
||||
placeholder="Enter name"
|
||||
bind:value={name} />
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
<Button submit>Send invite</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { Modal, InnerModal } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputText, Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
export let teamId: string;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let id: string;
|
||||
let name: string;
|
||||
let showDropdown = false;
|
||||
let error: string;
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
const project = await sdkForConsole.projects.create(id ?? 'unique()', name, teamId);
|
||||
dispatch('created', project);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${name} has been created`
|
||||
});
|
||||
id = name = null;
|
||||
showDropdown = false;
|
||||
show = false;
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal {error} size="big" bind:show>
|
||||
<svelte:fragment slot="header">Create Project</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText id="name" label="Name" bind:value={name} required autofocus={true} />
|
||||
{#if !showDropdown}
|
||||
<div>
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
Project ID
|
||||
</span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<InnerModal bind:show={showDropdown}>
|
||||
<svelte:fragment slot="title">Project ID</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Enter a custom project ID. Leave blank for a randomly generated one.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="form">
|
||||
<InputText
|
||||
id="id"
|
||||
label="Custom ID"
|
||||
showLabel={false}
|
||||
placeholder="Enter ID"
|
||||
autofocus={true}
|
||||
bind:value={id} />
|
||||
|
||||
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading
|
||||
underscore, period
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</InnerModal>
|
||||
{/if}
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { user } from '$lib/stores/user';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let showDelete = false;
|
||||
export let selectedMember: Models.Membership;
|
||||
|
||||
const deleteMembership = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.deleteMembership(selectedMember.teamId, selectedMember.$id);
|
||||
showDelete = false;
|
||||
dispatch('deleted');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${selectedMember.userName} was deleted from ${selectedMember.teamName}`
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$: isUser = selectedMember.userId === $user.$id;
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteMembership}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">
|
||||
{isUser ? 'Leave Organization' : 'Delete Member'}
|
||||
</svelte:fragment>
|
||||
<p>
|
||||
{isUser
|
||||
? `Are you sure you want to leave '${selectedMember.teamName}'?`
|
||||
: `Are you sure you want to delete ${selectedMember.userName} from '${selectedMember.teamName}'?`}
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>{isUser ? 'Leave' : 'Delete'}</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { newOrgModal, organization, organizationList } from '$lib/stores/organization';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
export let showDelete = false;
|
||||
|
||||
const deleteOrg = async () => {
|
||||
try {
|
||||
await sdkForConsole.teams.delete($organization.$id);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$organization.name} has been deleted`
|
||||
});
|
||||
if ($organizationList?.total > 1) {
|
||||
await organizationList.load();
|
||||
goto(`${base}/console/organization-${$organizationList.teams[0].$id}`);
|
||||
} else {
|
||||
newOrgModal.set(true);
|
||||
goto(`${base}/console`);
|
||||
}
|
||||
showDelete = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteOrg}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Organization</svelte:fragment>
|
||||
<p>
|
||||
Are you sure you want to delete <b>{$organization.name}</b>? All projects ({$organization.total})
|
||||
and data associated with this organization will be deleted. This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</Form>
|
||||
@@ -0,0 +1,150 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Bucket, EmptyBucket, Empty, Pagination } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import CreateOrganization from '../_createOrganization.svelte';
|
||||
import CreateProject from './_createProject.svelte';
|
||||
import { projectList } from '$lib/stores/organization';
|
||||
import { project } from '../project-[project]/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let projects: Models.Project[] = [];
|
||||
$: organizationId = $page.params.organization;
|
||||
|
||||
onMount(async () => {
|
||||
await projectList.load('', undefined, offset);
|
||||
projects = $projectList?.projects?.filter((n) => n.teamId === organizationId);
|
||||
});
|
||||
|
||||
let showCreate = false;
|
||||
let addOrganization = false;
|
||||
let offset = 0;
|
||||
const limit = 6;
|
||||
|
||||
const projectCreated = async (event: CustomEvent<Models.Project>) => {
|
||||
await project.load(event.detail.$id);
|
||||
await goto(`${base}/console/project-${event.detail.$id}`);
|
||||
};
|
||||
|
||||
const getPlatformInfo = (platform: string) => {
|
||||
let name: string, icon: string;
|
||||
if (platform.includes('flutter')) {
|
||||
name = 'Flutter';
|
||||
icon = 'flutter';
|
||||
} else if (platform.includes('apple')) {
|
||||
name = 'Apple';
|
||||
icon = 'apple';
|
||||
} else if (platform.includes('android')) {
|
||||
name = 'Android';
|
||||
icon = 'android';
|
||||
} else if (platform.includes('unity')) {
|
||||
name = 'Unity';
|
||||
icon = 'unity';
|
||||
} else if (platform.includes('web')) {
|
||||
name = 'Web';
|
||||
icon = 'code';
|
||||
} else {
|
||||
name = 'Unknown';
|
||||
icon = 'unknown';
|
||||
}
|
||||
return { name, icon };
|
||||
};
|
||||
|
||||
// $: projectList.load('', limit, offset);
|
||||
$: projects = $projectList?.projects?.filter((a) => a.teamId === organizationId);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<h2 class="heading-level-5">Projects</h2>
|
||||
|
||||
<Button
|
||||
on:click={() => {
|
||||
showCreate = true;
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" /> <span class="text">Create project</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if projects?.length}
|
||||
<ul
|
||||
class="grid-box common-section u-margin-block-start-32"
|
||||
style={`--grid-gap:1.5rem; --grid-item-size:${
|
||||
projects.length > 3 ? '22rem' : '25rem'
|
||||
};`}>
|
||||
{#each projects as project, index}
|
||||
{#if index >= offset && index < limit + offset}
|
||||
<Bucket href={`${base}/console/project-${project.$id}`}>
|
||||
<svelte:fragment slot="eyebrow"
|
||||
>{project?.platforms?.length ? project?.platforms?.length : 'No'} apps</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
{project.name}
|
||||
</svelte:fragment>
|
||||
{@const platformList = project.platforms.map((platform) => platform.type)}
|
||||
{@const platforms = Array.from(new Set(platformList))}
|
||||
{#each platforms as platform, i}
|
||||
{#if i < 3}
|
||||
<Pill>
|
||||
<span
|
||||
class={`icon-${getPlatformInfo(platform).icon}`}
|
||||
aria-hidden="true" />
|
||||
{getPlatformInfo(platform).name}
|
||||
</Pill>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if platforms?.length > 3}
|
||||
<Pill>
|
||||
+{project.platforms.length - 3}
|
||||
</Pill>
|
||||
{/if}
|
||||
</Bucket>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if projects.length < limit + offset && (projects.length % 2 !== 0 || projects.length % 4 === 0)}
|
||||
<EmptyBucket on:click={() => (showCreate = true)}>
|
||||
<div class="common-section">
|
||||
<Button secondary round>
|
||||
<i class="icon-plus" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<p>Create a new project</p>
|
||||
</div>
|
||||
</EmptyBucket>
|
||||
{/if}
|
||||
</ul>
|
||||
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {projects.length}</p>
|
||||
<Pagination {limit} bind:offset sum={projects.length} />
|
||||
</div>
|
||||
{:else}
|
||||
<Empty dashed centered>
|
||||
<div class="bucket">
|
||||
<div class="u-flex u-flex-vertical u-cross-center ">
|
||||
<div class="common-section">
|
||||
<Button secondary round on:click={() => (showCreate = true)}>
|
||||
<i class="icon-plus" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<p>Create a new project</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {projects?.length}</p>
|
||||
<Pagination {limit} bind:offset sum={projects?.length} />
|
||||
</div>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<CreateOrganization bind:show={addOrganization} />
|
||||
|
||||
<CreateProject bind:show={showCreate} teamId={organizationId} on:created={projectCreated} />
|
||||
@@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { Avatar, Pagination } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import Delete from './_deleteMember.svelte';
|
||||
import { organization, memberList, newMemberModal } from '$lib/stores/organization';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let search = '';
|
||||
let offset: number = null;
|
||||
|
||||
let selectedMember: Models.Membership;
|
||||
let showDelete = false;
|
||||
const url = `${$page.url.origin}/console/`;
|
||||
|
||||
const getAvatar = (name: string) =>
|
||||
sdkForConsole.avatars.getInitials(name, 120, 120).toString();
|
||||
const deleted = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0);
|
||||
const resend = async (member: Models.Membership) =>
|
||||
await sdkForConsole.teams.createMembership(
|
||||
$organization.$id,
|
||||
member.userEmail,
|
||||
member.roles,
|
||||
url,
|
||||
member.userName
|
||||
);
|
||||
|
||||
$: if (search) offset = 0;
|
||||
$: memberList.load($organization.$id, search, $pageLimit, offset ?? 0);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if $memberList?.total}
|
||||
<div class="u-flex u-gap-12 common-section u-main-space-between">
|
||||
<h2 class="heading-level-5">Members</h2>
|
||||
|
||||
<Button
|
||||
on:click={() => {
|
||||
newMemberModal.set(true);
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Invite</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Email</TableCellHead>
|
||||
<TableCellHead width={100}>Role</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $memberList.memberships as member}
|
||||
<TableRow>
|
||||
<TableCell title="Name">
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<Avatar
|
||||
size={40}
|
||||
src={getAvatar(member.userName)}
|
||||
name={member.userName} />
|
||||
<span class="text u-trim"
|
||||
>{member.userName ? member.userName : 'n/a'}</span>
|
||||
{#if member.invited && !member.joined}
|
||||
<Pill warning>Pending</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Email">{member.userEmail}</TableCellText>
|
||||
<TableCell title="Roles">
|
||||
{#if member.invited && !member.joined}
|
||||
<Button secondary on:click={() => resend(member)}>Resend</Button>
|
||||
{:else}
|
||||
{member.roles}
|
||||
{/if}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
aria-label="Delete item"
|
||||
disabled={$memberList.total === 1}
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showDelete = true;
|
||||
}}>
|
||||
<span class="icon-trash" aria-hidden="true" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$memberList.total}</p>
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$memberList.total} />
|
||||
</div>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
{#if selectedMember}
|
||||
<Delete {selectedMember} bind:showDelete on:deleted={() => deleted()} />
|
||||
{/if}
|
||||
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Box, AvatarGroup } from '$lib/components';
|
||||
import { InputText, Form, Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { organization, memberList } from '$lib/stores/organization';
|
||||
import { title, breadcrumbs } from '$lib/stores/layout';
|
||||
import Delete from './_deleteOrganization.svelte';
|
||||
|
||||
let name: string = $organization.name;
|
||||
let showDelete = false;
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForConsole.teams.update($organization.$id, name);
|
||||
$organization.name = name;
|
||||
title.set(name);
|
||||
const breadcrumb = $breadcrumbs.get(0);
|
||||
breadcrumb.title = name;
|
||||
$breadcrumbs = $breadcrumbs.set($breadcrumbs.size, breadcrumb);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
organization.subscribe((org) => {
|
||||
name = org?.name;
|
||||
});
|
||||
|
||||
let avatars = [];
|
||||
let avatarsTotal = 0;
|
||||
|
||||
memberList.subscribe((value) => {
|
||||
if (value?.total > 0) {
|
||||
avatarsTotal = value.total;
|
||||
avatars = value.memberships.map((team) => {
|
||||
return {
|
||||
name: team.userName,
|
||||
img: sdkForConsole.avatars.getInitials(team.userName, 120, 120).toString()
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if $organization}
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={name} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={name === $organization.name || !name} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Delete Organization</h6>
|
||||
</div>
|
||||
<p>
|
||||
The organization will be permanently deleted, including all projects and data
|
||||
associated with this organization. This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
<AvatarGroup {avatars} total={avatarsTotal} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold">{$organization.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>{$organization.total} projects</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete />
|
||||
+10
-10
@@ -4,7 +4,8 @@
|
||||
import { sdkForConsole, setProject } from '$lib/stores/sdk';
|
||||
import { collection } from './databases/database/[database]/collection/[collection]/store';
|
||||
import { UploadBox } from '$lib/components';
|
||||
import { organisation, project } from './store';
|
||||
import { project } from './store';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { onMount } from 'svelte';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
@@ -47,28 +48,27 @@
|
||||
afterNavigate(handle);
|
||||
|
||||
let loaded = false;
|
||||
const path = 'console';
|
||||
async function handle(event = null) {
|
||||
if ($project?.$id !== projectId) {
|
||||
setProject(projectId);
|
||||
await project.load(projectId);
|
||||
}
|
||||
if ($organisation?.$id !== $project.teamId) {
|
||||
await organisation.load($project.teamId);
|
||||
if ($organization?.$id !== $project?.teamId) {
|
||||
await organization.load($project.teamId);
|
||||
}
|
||||
}
|
||||
|
||||
updateLayout({
|
||||
navigate: event,
|
||||
title: $project.name,
|
||||
level: 2,
|
||||
level: 1,
|
||||
breadcrumbs: [
|
||||
{
|
||||
href: path,
|
||||
title: $organisation.name,
|
||||
level: 1
|
||||
href: 'console',
|
||||
title: $organization.name,
|
||||
level: 0
|
||||
},
|
||||
{
|
||||
href: $project.$id,
|
||||
href: `project-${$project.$id}`,
|
||||
title: $project.name
|
||||
}
|
||||
]
|
||||
+7
-7
@@ -3,7 +3,7 @@
|
||||
import { updateLayout } from '$lib/stores/layout';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const path = 'users';
|
||||
const path = 'authentication';
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
@@ -13,10 +13,10 @@
|
||||
function handle(event = null) {
|
||||
updateLayout({
|
||||
navigate: event,
|
||||
title: 'Users',
|
||||
title: 'Authentication',
|
||||
level: 3,
|
||||
breadcrumbs: {
|
||||
title: 'Users',
|
||||
title: 'Authentication',
|
||||
href: path
|
||||
},
|
||||
tabs: [
|
||||
@@ -28,10 +28,6 @@
|
||||
href: `${path}/teams`,
|
||||
title: 'Teams'
|
||||
},
|
||||
{
|
||||
href: `${path}/authentication`,
|
||||
title: 'Authentication'
|
||||
},
|
||||
{
|
||||
href: `${path}/usage`,
|
||||
title: 'Usage'
|
||||
@@ -39,6 +35,10 @@
|
||||
{
|
||||
href: `${path}/security`,
|
||||
title: 'Security'
|
||||
},
|
||||
{
|
||||
href: `${path}/settings`,
|
||||
title: 'Settings'
|
||||
}
|
||||
]
|
||||
});
|
||||
+11
-6
@@ -17,6 +17,7 @@
|
||||
export let provider: Provider;
|
||||
|
||||
let { keyID, teamID, p8 } = JSON.parse(provider.secret);
|
||||
let error: string;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const update = async () => {
|
||||
@@ -29,24 +30,28 @@
|
||||
secret
|
||||
);
|
||||
showModal = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.active ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal size="big" bind:show={showModal}>
|
||||
<Modal {error} size="big" bind:show={showModal}>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For
|
||||
more info you can
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer">
|
||||
visit the docs.
|
||||
</a>
|
||||
</p>
|
||||
<InputSwitch
|
||||
id="state"
|
||||
+9
-5
@@ -9,14 +9,15 @@
|
||||
Form,
|
||||
FormList
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let showModal = false;
|
||||
export let provider: Provider;
|
||||
|
||||
let { clientSecret, auth0Domain } = JSON.parse(provider.secret);
|
||||
let error: string;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
const update = async () => {
|
||||
@@ -29,17 +30,20 @@
|
||||
secret
|
||||
);
|
||||
showModal = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.active ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal size="big" bind:show={showModal}>
|
||||
<Modal {error} size="big" bind:show={showModal}>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
+8
-8
@@ -10,7 +10,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let name: string, id: string;
|
||||
let name: string, id: string, error: string;
|
||||
let showDropdown = false;
|
||||
|
||||
const create = async () => {
|
||||
@@ -24,11 +24,8 @@
|
||||
message: `${team.name} has been created`
|
||||
});
|
||||
dispatch('created', team);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,11 +34,12 @@
|
||||
}
|
||||
$: if (!showCreate) {
|
||||
showDropdown = false;
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal size="big" bind:show={showCreate}>
|
||||
<Modal {error} size="big" bind:show={showCreate}>
|
||||
<svelte:fragment slot="header">Create Team</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
@@ -60,7 +58,9 @@
|
||||
{:else}
|
||||
<InnerModal bind:show={showDropdown}>
|
||||
<svelte:fragment slot="title">Team ID</svelte:fragment>
|
||||
Enter a custom team ID. Leave blank for a randomly generated one.
|
||||
<svelte:fragment slot="subtitle">
|
||||
Enter a custom team ID. Leave blank for a randomly generated one.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="form">
|
||||
<InputText
|
||||
+16
-13
@@ -19,6 +19,7 @@
|
||||
|
||||
let showDropdown = false;
|
||||
let name: string, mail: string, pass: string, id: string;
|
||||
let error: string;
|
||||
|
||||
const create = async () => {
|
||||
try {
|
||||
@@ -31,11 +32,8 @@
|
||||
message: `${user.name} has been created`
|
||||
});
|
||||
dispatch('created', user);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,11 +42,12 @@
|
||||
}
|
||||
$: if (!showCreate) {
|
||||
showDropdown = false;
|
||||
error = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal size="big" bind:show={showCreate}>
|
||||
<Modal {error} size="big" bind:show={showCreate}>
|
||||
<svelte:fragment slot="header">Create User</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
@@ -73,15 +72,18 @@
|
||||
|
||||
{#if !showDropdown}
|
||||
<div>
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}
|
||||
><span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
User ID
|
||||
</span></Pill>
|
||||
</span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<InnerModal bind:show={showDropdown}>
|
||||
<svelte:fragment slot="title">User ID</svelte:fragment>
|
||||
Enter a custom user ID. Leave blank for a randomly generated one.
|
||||
<svelte:fragment slot="subtitle">
|
||||
Enter a custom user ID. Leave blank for a randomly generated one.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="form">
|
||||
<InputText
|
||||
@@ -96,9 +98,10 @@
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5"
|
||||
>Allowed characters: alphanumeric, hyphen, non-leading
|
||||
underscore, period</span>
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading
|
||||
underscore, period
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
+12
-5
@@ -9,13 +9,17 @@
|
||||
Form,
|
||||
FormList
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let showModal = false;
|
||||
export let provider: Provider;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
let error: string;
|
||||
|
||||
const update = async () => {
|
||||
try {
|
||||
await sdkForConsole.projects.updateOAuth2(
|
||||
@@ -25,17 +29,20 @@
|
||||
provider.secret
|
||||
);
|
||||
showModal = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.active ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal size="big" bind:show={showModal}>
|
||||
<Modal {error} size="big" bind:show={showModal}>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
+14
-7
@@ -9,15 +9,18 @@
|
||||
Form,
|
||||
FormList
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let showModal = false;
|
||||
export let provider: Provider;
|
||||
|
||||
let { clientSecret, tenantID } = JSON.parse(provider.secret);
|
||||
let error: string;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
const update = async () => {
|
||||
try {
|
||||
const secret = JSON.stringify({ clientSecret, tenantID });
|
||||
@@ -29,24 +32,28 @@
|
||||
);
|
||||
|
||||
showModal = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.active ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal size="big" bind:show={showModal}>
|
||||
<Modal {error} size="big" bind:show={showModal}>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For
|
||||
more info you can
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
<a class="link" href={provider.docs} target="_blank" rel="noopener noreferrer">
|
||||
visit the docs.
|
||||
</a>
|
||||
</p>
|
||||
<InputSwitch
|
||||
id="state"
|
||||
+10
-6
@@ -9,16 +9,18 @@
|
||||
Form,
|
||||
FormList
|
||||
} from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
|
||||
export let showModal = false;
|
||||
export let provider: Provider;
|
||||
|
||||
let { clientSecret, oktaDomain, authorizationServerId } = JSON.parse(provider.secret);
|
||||
let error: string;
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
const update = async () => {
|
||||
try {
|
||||
const secret = JSON.stringify({ clientSecret, oktaDomain, authorizationServerId });
|
||||
@@ -28,19 +30,21 @@
|
||||
provider.id,
|
||||
secret
|
||||
);
|
||||
|
||||
showModal = false;
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
type: 'success',
|
||||
message: `${provider.name} authentication has been ${
|
||||
provider.active ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={update}>
|
||||
<Modal size="big" bind:show={showModal}>
|
||||
<Modal {error} size="big" bind:show={showModal}>
|
||||
<svelte:fragment slot="header">{provider.name} OAuth2 Settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
+7
-6
@@ -21,20 +21,20 @@
|
||||
import { base } from '$app/paths';
|
||||
import { usersList } from './store';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
|
||||
let showCreate = false;
|
||||
let search = '';
|
||||
let offset = 0;
|
||||
|
||||
const limit = 5;
|
||||
const project = $page.params.project;
|
||||
const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 32, 32).toString();
|
||||
const userCreated = async (event: CustomEvent<Models.User<Record<string, unknown>>>) => {
|
||||
await goto(`${base}/console/${project}/users/user/${event.detail.$id}`);
|
||||
await goto(`${base}/console/project-${project}/authentication/user/${event.detail.$id}`);
|
||||
};
|
||||
|
||||
$: if (search) offset = 0;
|
||||
$: usersList.load(search, limit, offset ?? 0);
|
||||
$: usersList.load(search, $pageLimit, offset ?? 0);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -63,7 +63,8 @@
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $usersList.users as user}
|
||||
<TableRowLink href={`${base}/console/${project}/users/user/${user.$id}`}>
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${project}/authentication/user/${user.$id}`}>
|
||||
<TableCell title="Name">
|
||||
<div class="u-flex u-gap-12 u-cross-center">
|
||||
<Avatar size={32} src={getAvatar(user.name)} name={user.name} />
|
||||
@@ -98,7 +99,7 @@
|
||||
</Table>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$usersList.total}</p>
|
||||
<Pagination {limit} bind:offset sum={$usersList.total} />
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$usersList.total} />
|
||||
</div>
|
||||
{:else if search}
|
||||
<Empty>
|
||||
@@ -114,7 +115,7 @@
|
||||
</Empty>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$usersList.total}</p>
|
||||
<Pagination {limit} bind:offset sum={$usersList.total} />
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$usersList.total} />
|
||||
</div>
|
||||
{:else}
|
||||
<Empty dashed centered>
|
||||
+6
-4
@@ -72,8 +72,9 @@
|
||||
<CardGrid>
|
||||
<h2 class="heading-level-6">Users Limit</h2>
|
||||
<p>
|
||||
This limits new users from signing up for your project, regardless of authentication
|
||||
method. You can still create users from your Appwrite console.
|
||||
Limit new users from signing up for your project, regardless of authentication method.
|
||||
You can still create users and team memberships from your Appwrite console. <b>
|
||||
The maximum limit is 10,000 users.</b>
|
||||
</p>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
@@ -135,7 +136,7 @@
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<h2 class="heading-level-6">Session Length</h2>
|
||||
<h2 class="heading-level-6">Session Length (Coming Soon)</h2>
|
||||
<p>
|
||||
If you reduce the limit, users who are currently logged in will be logged out of the
|
||||
application.
|
||||
@@ -143,8 +144,9 @@
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list is-multiple">
|
||||
<InputNumber id="length" label="Length" value={1} />
|
||||
<InputNumber disabled id="length" label="Length" value={1} />
|
||||
<InputSelect
|
||||
disabled
|
||||
id="period"
|
||||
{options}
|
||||
label="Time Period"
|
||||
+8
-6
@@ -6,7 +6,7 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdkForConsole } from '$lib/stores/sdk';
|
||||
import { project } from '../store';
|
||||
import { authMethods } from '$lib/stores/auth-methods';
|
||||
import { authMethods, type AuthMethod } from '$lib/stores/auth-methods';
|
||||
import { OAuthProviders } from '$lib/stores/oauth-providers';
|
||||
import { event } from '$lib/actions/analytics';
|
||||
import type { Provider } from '$lib/stores/oauth-providers';
|
||||
@@ -17,12 +17,14 @@
|
||||
$: OAuthProviders.load($project);
|
||||
let showModal = false;
|
||||
|
||||
const authUpdate = async (id: string, value: boolean) => {
|
||||
const authUpdate = async (box: AuthMethod) => {
|
||||
try {
|
||||
await sdkForConsole.projects.updateAuthStatus(projectId, id, value);
|
||||
await sdkForConsole.projects.updateAuthStatus(projectId, box.method, box.value);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Updated project authentication status successfully'
|
||||
message: `${box.label} authentication has been ${
|
||||
box.value ? 'enabled' : 'disabled'
|
||||
}`
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -50,7 +52,7 @@
|
||||
id={box.method}
|
||||
bind:value={box.value}
|
||||
on:change={() => {
|
||||
authUpdate(box.method, box.value);
|
||||
authUpdate(box);
|
||||
}} />
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -85,7 +87,7 @@
|
||||
<p class="u-margin-block-start-8">{provider.name}</p>
|
||||
<div class="u-margin-block-start-24">
|
||||
<Pill success={provider.active}>
|
||||
{provider.active ? 'Enabled' : 'Disabled'}
|
||||
{provider.active ? 'enabled' : 'disabled'}
|
||||
</Pill>
|
||||
</div>
|
||||
</button>
|
||||
+3
-2
@@ -29,7 +29,7 @@
|
||||
const project = $page.params.project;
|
||||
const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 32, 32).toString();
|
||||
const teamCreated = async (event: CustomEvent<Models.Team>) => {
|
||||
await goto(`${base}/console/${project}/users/teams/${event.detail.$id}`);
|
||||
await goto(`${base}/console/project-${project}/authentication/teams/${event.detail.$id}`);
|
||||
};
|
||||
|
||||
$: if (search) offset = 0;
|
||||
@@ -60,7 +60,8 @@
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $teamsList.teams as team}
|
||||
<TableRowLink href={`${base}/console/${project}/users/teams/${team.$id}`}>
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${project}/authentication/teams/${team.$id}`}>
|
||||
<TableCell title="ID">
|
||||
<div class="u-flex u-gap-12">
|
||||
<Avatar size={32} name={team.name} src={getAvatar(team.name)} />
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const teamId = $page.params.team;
|
||||
const path = `users/teams/${teamId}`;
|
||||
const path = `authentication/teams/${teamId}`;
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
@@ -21,7 +21,7 @@
|
||||
navigate: event,
|
||||
level: 4,
|
||||
title: $team.name,
|
||||
back: `${base}/console/${$page.params.project}/users/teams`,
|
||||
back: `${base}/console/project-${$page.params.project}/authentication/teams`,
|
||||
breadcrumbs: {
|
||||
title: $team.name,
|
||||
href: `teams/${teamId}`
|
||||
+6
-8
@@ -11,10 +11,11 @@
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let name: string, email: string, roles: [];
|
||||
let name: string, email: string, roles: string[];
|
||||
let error: string;
|
||||
|
||||
const create = async () => {
|
||||
const url = `${$page.url.origin}/console/${$page.params.project}/users/teams/${$page.params.team}/members`;
|
||||
const url = `${$page.url.origin}/console/project-${$page.params.project}/authentication/teams/${$page.params.team}/members`;
|
||||
|
||||
try {
|
||||
const user = await sdkForProject.teams.createMembership(
|
||||
@@ -32,17 +33,14 @@
|
||||
roles = [];
|
||||
showCreate = false;
|
||||
dispatch('created', user);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
} catch ({ message }) {
|
||||
error = message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Form on:submit={create}>
|
||||
<Modal bind:show={showCreate}>
|
||||
<Modal {error} size="big" bind:show={showCreate}>
|
||||
<svelte:fragment slot="header">Create Membership</svelte:fragment>
|
||||
<FormList>
|
||||
<InputEmail
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
showDelete = false;
|
||||
dispatch('deleted');
|
||||
await goto(
|
||||
`${base}/console/${$page.params.project}/users/user/${selectedMembership.userId}/memberships`
|
||||
`${base}/console/project-${$page.params.project}/authentication/teams/${selectedMembership.teamId}/members`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -35,7 +35,7 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteMembership}>
|
||||
<Modal warning={true} bind:show={showDelete}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Member</svelte:fragment>
|
||||
<p>
|
||||
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
try {
|
||||
await sdkForProject.teams.delete(team.$id);
|
||||
showDelete = false;
|
||||
await goto(`${base}/console/${$page.params.project}/users/teams`);
|
||||
await goto(`${base}/console/project-${$page.params.project}/authentication/teams`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
@@ -26,7 +26,7 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteTeam}>
|
||||
<Modal warning={true} bind:show={showDelete}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Team</svelte:fragment>
|
||||
|
||||
<p>
|
||||
+35
-25
@@ -8,17 +8,21 @@
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
|
||||
let offset = 0;
|
||||
const limit = 5;
|
||||
|
||||
$: request = sdkForProject.teams.listLogs($page.params.team, limit, offset);
|
||||
$: request = sdkForProject.teams.listLogs($page.params.team, $pageLimit, offset);
|
||||
|
||||
const getBrowser = (clientCode: string) => {
|
||||
return sdkForProject.avatars.getBrowser(clientCode, 80, 80);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -37,25 +41,33 @@
|
||||
<TableBody>
|
||||
{#each response.logs as log}
|
||||
<TableRow>
|
||||
<TableCellText title="Client">
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="image-item">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={`/icons/${
|
||||
$app.themeInUse
|
||||
}/color/${log?.clientName.toLocaleLowerCase()}.svg`}
|
||||
alt={log.clientName} />
|
||||
<TableCell title="Client">
|
||||
{#if log.clientName}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(log.clientCode).toString()}
|
||||
alt={log.clientName} />
|
||||
</div>
|
||||
<p class="text u-trim">
|
||||
{log.clientName}
|
||||
{log.clientVersion}
|
||||
on {log.osName}
|
||||
{log.osVersion}
|
||||
</p>
|
||||
</div>
|
||||
<p class="u-line-height-1-5">
|
||||
{log.clientName}
|
||||
{log.clientVersion}
|
||||
on {log.osName}
|
||||
{log.osVersion}
|
||||
</p>
|
||||
</div>
|
||||
</TableCellText>
|
||||
{:else}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<p class="text u-trim">
|
||||
<span class="avatar is-color-empty" />
|
||||
|
||||
Unknown
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCellText title="Event">{log.event}</TableCellText>
|
||||
|
||||
<TableCellText title="Location">
|
||||
@@ -84,11 +96,9 @@
|
||||
</div>
|
||||
</Empty>
|
||||
{/if}
|
||||
<div
|
||||
class="u-flex u-margin-block-start-32
|
||||
u-main-space-between">
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {response.total}</p>
|
||||
<Pagination {limit} bind:offset sum={response.total} />
|
||||
<Pagination limit={$pageLimit} bind:offset sum={response.total} />
|
||||
</div>
|
||||
{/await}
|
||||
</Container>
|
||||
+31
-35
@@ -2,21 +2,18 @@
|
||||
import { page } from '$app/stores';
|
||||
import { Avatar, CardGrid, Box } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { Button, InputText, Helper } from '$lib/elements/forms';
|
||||
import { Button, InputText, Form } from '$lib/elements/forms';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { team } from './store';
|
||||
import { title } from '$lib/stores/layout';
|
||||
import { title, breadcrumbs } from '$lib/stores/layout';
|
||||
import DeleteTeam from './_deleteTeam.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 48, 48).toString();
|
||||
|
||||
let showDelete = false;
|
||||
let showError: false | 'name' | 'email' | 'password' = false;
|
||||
let errorMessage = 'Something went wrong';
|
||||
let errorType: 'error' | 'warning' | 'success' = 'error';
|
||||
let teamName: string = null;
|
||||
|
||||
onMount(async () => {
|
||||
@@ -24,23 +21,23 @@
|
||||
teamName ??= $team.name;
|
||||
});
|
||||
|
||||
function addError(location: typeof showError, message: string, type: typeof errorType) {
|
||||
showError = location;
|
||||
errorMessage = message;
|
||||
errorType = type;
|
||||
}
|
||||
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.teams.update($page.params.team, teamName);
|
||||
$team.name = teamName;
|
||||
title.set(teamName);
|
||||
const breadcrumb = $breadcrumbs.get($breadcrumbs.size);
|
||||
breadcrumb.title = teamName;
|
||||
$breadcrumbs = $breadcrumbs.set($breadcrumbs.size, breadcrumb);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addError('name', error.message, 'error');
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -62,31 +59,30 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter team name"
|
||||
autocomplete={false}
|
||||
bind:value={teamName} />
|
||||
{#if showError === 'name'}
|
||||
<Helper type={errorType}>{errorMessage}</Helper>
|
||||
{/if}
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter team name"
|
||||
autocomplete={false}
|
||||
bind:value={teamName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={teamName === $team.name || !teamName}
|
||||
on:click={() => {
|
||||
updateName();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={teamName === $team.name || !teamName}
|
||||
on:click={() => {
|
||||
updateName();
|
||||
}}>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid>
|
||||
<div>
|
||||
+10
-9
@@ -19,9 +19,10 @@
|
||||
import { memberships } from './store';
|
||||
import CreateMember from './_createMembership.svelte';
|
||||
import DeleteMembership from './_deleteMembership.svelte';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
|
||||
const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 32, 32).toString();
|
||||
const deleted = () => memberships.load($page.params.team, search, limit, offset ?? 0);
|
||||
const deleted = () => memberships.load($page.params.team, search, $pageLimit, offset ?? 0);
|
||||
|
||||
const project = $page.params.project;
|
||||
|
||||
@@ -31,14 +32,14 @@
|
||||
let offset: number = null;
|
||||
let selectedMembership: Models.Membership;
|
||||
|
||||
const limit = 5;
|
||||
|
||||
$: if (search) offset = 0;
|
||||
$: memberships.load($page.params.team, search, limit, offset ?? 0);
|
||||
$: memberships.load($page.params.team, search, $pageLimit, offset ?? 0);
|
||||
|
||||
const memberCreated = async (event: CustomEvent<Models.Membership>) => {
|
||||
memberships.load($page.params.team, search, limit, offset ?? 0);
|
||||
await goto(`${base}/console/${project}/users/teams/${event.detail.teamId}/members`);
|
||||
memberships.load($page.params.team, search, $pageLimit, offset ?? 0);
|
||||
await goto(
|
||||
`${base}/console/project-${project}/authentication/teams/${event.detail.teamId}/members`
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -60,7 +61,7 @@
|
||||
<TableBody>
|
||||
{#each $memberships.memberships as membership}
|
||||
<TableRowLink
|
||||
href={`${base}/console/${project}/users/user/${membership.userId}`}>
|
||||
href={`${base}/console/project-${project}/authentication/user/${membership.userId}`}>
|
||||
<TableCellText title="Name">
|
||||
<div class="u-flex u-gap-12">
|
||||
<Avatar
|
||||
@@ -93,7 +94,7 @@
|
||||
class="u-flex u-margin-block-start-32
|
||||
u-main-space-between">
|
||||
<p class="text">Total results: {$memberships.total}</p>
|
||||
<Pagination {limit} bind:offset sum={$memberships.total} />
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$memberships.total} />
|
||||
</div>
|
||||
{:else if search}
|
||||
<Empty>
|
||||
@@ -111,7 +112,7 @@
|
||||
class="u-flex u-margin-block-start-32
|
||||
u-main-space-between">
|
||||
<p class="text">Total results: {$memberships.total}</p>
|
||||
<Pagination {limit} bind:offset sum={$memberships.total} />
|
||||
<Pagination limit={$pageLimit} bind:offset sum={$memberships.total} />
|
||||
</div>
|
||||
{:else}
|
||||
<Empty dashed centered>
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
import { user } from './store';
|
||||
|
||||
const userId = $page.params.user;
|
||||
const path = `users/user/${userId}`;
|
||||
const path = `authentication/user/${userId}`;
|
||||
|
||||
onMount(handle);
|
||||
afterNavigate(handle);
|
||||
@@ -25,7 +25,7 @@
|
||||
title: $user.name,
|
||||
href: `user/${userId}`
|
||||
},
|
||||
back: `${base}/console/${$page.params.project}/users`,
|
||||
back: `${base}/console/project-${$page.params.project}/authentication`,
|
||||
copy: {
|
||||
text: 'User ID',
|
||||
value: userId
|
||||
+4
-2
@@ -14,7 +14,9 @@
|
||||
try {
|
||||
await sdkForProject.teams.deleteMembership('tmpstring', 'tmpstring2');
|
||||
showDeleteAll = false;
|
||||
await goto(`${base}/console/${$page.params.project}/users/${$user.$id}/membeships`);
|
||||
await goto(
|
||||
`${base}/console/project-${$page.params.project}/authentication/${$user.$id}/membeships`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
@@ -25,7 +27,7 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteAllMemberships}>
|
||||
<Modal warning={true} bind:show={showDeleteAll}>
|
||||
<Modal bind:show={showDeleteAll} warning>
|
||||
<svelte:fragment slot="header">Delete All Memberships</svelte:fragment>
|
||||
|
||||
<p>
|
||||
+1
-2
@@ -26,9 +26,8 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteAllSessions}>
|
||||
<Modal warning={true} bind:show={showDeleteAll}>
|
||||
<Modal bind:show={showDeleteAll} warning>
|
||||
<svelte:fragment slot="header">Delete All Sessions</svelte:fragment>
|
||||
|
||||
<p>
|
||||
Are you sure you want to delete <b>all of {$user.name}'s sessions?</b>
|
||||
</p>
|
||||
+2
-3
@@ -23,7 +23,7 @@
|
||||
showDelete = false;
|
||||
dispatch('deleted');
|
||||
await goto(
|
||||
`${base}/console/${$page.params.project}/users/teams/${selectedMembership.teamId}/members`
|
||||
`${base}/console/project-${$page.params.project}/authentication/user/${selectedMembership.userId}/memberships`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -35,9 +35,8 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteMembership}>
|
||||
<Modal warning={true} bind:show={showDelete}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Member</svelte:fragment>
|
||||
|
||||
<p>
|
||||
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
|
||||
</p>
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteSession}>
|
||||
<Modal warning={true} bind:show={showDelete}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete Sessions</svelte:fragment>
|
||||
|
||||
<p>Are you sure you want to delete this session?</p>
|
||||
+2
-2
@@ -19,7 +19,7 @@
|
||||
type: 'success',
|
||||
message: `${$user.name} has been deleted`
|
||||
});
|
||||
await goto(`${base}/console/${$page.params.project}/users`);
|
||||
await goto(`${base}/console/project-${$page.params.project}/authentication`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
@@ -30,7 +30,7 @@
|
||||
</script>
|
||||
|
||||
<Form on:submit={deleteUser}>
|
||||
<Modal warning={true} bind:show={showDelete}>
|
||||
<Modal bind:show={showDelete} warning>
|
||||
<svelte:fragment slot="header">Delete User</svelte:fragment>
|
||||
<p>Are you sure you want to delete <b>{$user.name}</b> from '{$project.name}'?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
+34
-32
@@ -8,17 +8,21 @@
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableCellHead,
|
||||
TableCell,
|
||||
TableCellText
|
||||
} from '$lib/elements/table';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { pageLimit } from '$lib/stores/layout';
|
||||
|
||||
let offset = 0;
|
||||
const limit = 25;
|
||||
|
||||
$: request = sdkForProject.users.getLogs($page.params.user, limit, offset);
|
||||
$: request = sdkForProject.users.getLogs($page.params.user, $pageLimit, offset);
|
||||
|
||||
const getBrowser = (clientCode: string) => {
|
||||
return sdkForProject.avatars.getBrowser(clientCode, 80, 80);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -37,38 +41,38 @@
|
||||
<TableBody>
|
||||
{#each response.logs as log}
|
||||
<TableRow>
|
||||
<TableCellText title="Client">
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="image-item">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={`/icons/${
|
||||
$app.themeInUse
|
||||
}/color/${log?.clientName.toLocaleLowerCase()}.svg`}
|
||||
alt={log.clientName} />
|
||||
</div>
|
||||
<span class="u-line-height-1-5">
|
||||
<p>
|
||||
<TableCell title="Client">
|
||||
{#if log.clientName}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<div class="avatar is-small">
|
||||
<img
|
||||
height="20"
|
||||
width="20"
|
||||
src={getBrowser(log.clientCode).toString()}
|
||||
alt={log.clientName} />
|
||||
</div>
|
||||
<p class="text u-trim">
|
||||
{log.clientName}
|
||||
{log.clientVersion}
|
||||
</p>
|
||||
<span class="u-small">
|
||||
on {log.osName}
|
||||
{log.osVersion}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</TableCellText>
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="u-flex u-cross-center u-gap-12">
|
||||
<p class="text u-trim">
|
||||
<span class="avatar is-color-empty" />
|
||||
|
||||
Unknown
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCellText title="Event">{log.event}</TableCellText>
|
||||
|
||||
<TableCellText title="Location">
|
||||
{#if log.countryCode !== '--'}
|
||||
<img
|
||||
src={sdkForProject.avatars
|
||||
.getFlag(log.countryCode, 32, 32)
|
||||
.toString()}
|
||||
alt={log.countryName} />{log.countryName}
|
||||
{log.countryName}
|
||||
{:else}
|
||||
Unknown
|
||||
{/if}
|
||||
@@ -89,17 +93,15 @@
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/users?sdk=nodejs-default#usersGetLogs"
|
||||
href="https://appwrite.io/docs/server/authentication?sdk=nodejs-default#usersGetLogs"
|
||||
>Documentation</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Empty>
|
||||
{/if}
|
||||
<div
|
||||
class="u-flex u-margin-block-start-32
|
||||
u-main-space-between">
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {response.total}</p>
|
||||
<Pagination {limit} bind:offset sum={response.total} />
|
||||
<Pagination limit={$pageLimit} bind:offset sum={response.total} />
|
||||
</div>
|
||||
{/await}
|
||||
</Container>
|
||||
@@ -0,0 +1,474 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Avatar, CardGrid, Box, DropList, DropListItem } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
InputText,
|
||||
InputEmail,
|
||||
InputPassword,
|
||||
InputPhone
|
||||
} from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import DeleteUser from './_deleteUser.svelte';
|
||||
import { user } from './store';
|
||||
import { onMount } from 'svelte';
|
||||
import { title, breadcrumbs } from '$lib/stores/layout';
|
||||
|
||||
$: if (prefs) {
|
||||
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($user.prefs))) {
|
||||
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
|
||||
arePrefsDisabled = false;
|
||||
}
|
||||
} else {
|
||||
arePrefsDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let showDelete = false;
|
||||
let userName: string = null,
|
||||
userEmail: string = null,
|
||||
userPhone: string = null,
|
||||
newPassword: string = null;
|
||||
let prefs: [string, unknown][] = null;
|
||||
let arePrefsDisabled = true;
|
||||
let showVerifcationDropdown = false;
|
||||
|
||||
onMount(async () => {
|
||||
await user.load($page.params.user);
|
||||
prefs = Object.entries($user.prefs);
|
||||
if (!prefs?.length) {
|
||||
prefs.push(['', '']);
|
||||
}
|
||||
userName ??= $user.name;
|
||||
userEmail ??= $user.email;
|
||||
userPhone ??= $user.phone;
|
||||
});
|
||||
|
||||
const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 48, 48).toString();
|
||||
|
||||
async function updateVerificationEmail() {
|
||||
showVerifcationDropdown = false;
|
||||
try {
|
||||
await sdkForProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
|
||||
$user.emailVerification = !$user.emailVerification;
|
||||
addNotification({
|
||||
message: `The account has been ${
|
||||
$user.emailVerification ? 'verified' : 'unverified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateVerificationPhone() {
|
||||
showVerifcationDropdown = false;
|
||||
try {
|
||||
await sdkForProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
|
||||
$user.phoneVerification = !$user.phoneVerification;
|
||||
addNotification({
|
||||
message: `The account has been ${
|
||||
$user.phoneVerification ? 'verified' : 'unverified'
|
||||
}`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateStatus() {
|
||||
try {
|
||||
await sdkForProject.users.updateStatus($user.$id, !$user.status);
|
||||
$user.status = !$user.status;
|
||||
addNotification({
|
||||
message: `The account has been ${$user.status ? 'unblocked' : 'blocked'}`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateName() {
|
||||
try {
|
||||
await sdkForProject.users.updateName($user.$id, userName);
|
||||
$user.name = userName;
|
||||
title.set(userName);
|
||||
const breadcrumb = $breadcrumbs.get($breadcrumbs.size);
|
||||
breadcrumb.title = userName;
|
||||
$breadcrumbs = $breadcrumbs.set($breadcrumbs.size, breadcrumb);
|
||||
addNotification({
|
||||
message: 'Name has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updateEmail() {
|
||||
try {
|
||||
await sdkForProject.users.updateEmail($user.$id, userEmail);
|
||||
$user.email = userEmail;
|
||||
addNotification({
|
||||
message: 'Email has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updatePhone() {
|
||||
try {
|
||||
await sdkForProject.users.updatePhone($user.$id, userPhone);
|
||||
$user.phone = userPhone;
|
||||
addNotification({
|
||||
message: 'Phone has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
async function updatePassword() {
|
||||
try {
|
||||
await sdkForProject.users.updatePassword($user.$id, newPassword);
|
||||
newPassword = null;
|
||||
addNotification({
|
||||
message: 'Password has been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePrefs() {
|
||||
try {
|
||||
let updatedPrefs = Object.fromEntries(prefs);
|
||||
|
||||
await sdkForProject.users.updatePrefs($user.$id, updatedPrefs);
|
||||
$user.prefs = updatedPrefs;
|
||||
arePrefsDisabled = true;
|
||||
|
||||
addNotification({
|
||||
message: 'Preferences have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $user}
|
||||
<Container>
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16">
|
||||
<Avatar size={48} name={$user.name} src={getAvatar($user.name)} />
|
||||
<h6 class="heading-level-7">{$user.name}</h6>
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div>
|
||||
{#if $user.email}
|
||||
<p class="title">{$user.email}</p>
|
||||
{/if}
|
||||
{#if $user.phone}
|
||||
<p class="title">{$user.phone}</p>
|
||||
{/if}
|
||||
<p>Joined: {toLocaleDateTime($user.registration)}</p>
|
||||
</div>
|
||||
{#if !$user.status}
|
||||
<Pill danger>blocked</Pill>
|
||||
{:else if $user.email && $user.phone}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification && $user.phoneVerification
|
||||
? 'verified'
|
||||
: $user.emailVerification
|
||||
? 'verified email'
|
||||
: $user.phoneVerification
|
||||
? 'verified phone'
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{:else}
|
||||
<Pill success={$user.emailVerification || $user.phoneVerification}>
|
||||
{$user.emailVerification
|
||||
? 'verified '
|
||||
: $user.phoneVerification
|
||||
? 'verified '
|
||||
: 'unverified'}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
text={$user.status}
|
||||
secondary={!$user.status}
|
||||
on:click={() => updateStatus()}
|
||||
>{$user.status ? 'Block account' : 'Unblock account'}
|
||||
</Button>
|
||||
{#if $user.status}
|
||||
{#if $user.phone && $user.email}
|
||||
<DropList
|
||||
bind:show={showVerifcationDropdown}
|
||||
position="top"
|
||||
horizontal="left"
|
||||
arrow={true}
|
||||
arrowPosition="end">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() =>
|
||||
(showVerifcationDropdown = !showVerifcationDropdown)}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="mail"
|
||||
on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} email
|
||||
</DropListItem>
|
||||
<DropListItem
|
||||
icon="phone"
|
||||
on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} phone
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{:else if !$user.phone}
|
||||
<Button secondary on:click={() => updateVerificationEmail()}>
|
||||
{$user.emailVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>
|
||||
{:else if !$user.email}
|
||||
<Button secondary on:click={() => updateVerificationPhone()}>
|
||||
{$user.phoneVerification ? 'Unverify' : 'Verify'} account
|
||||
</Button>{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Form on:submit={updateName}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Name</h6>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={userName} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userName === $user.name || !userName} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updateEmail}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Email</h6>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputEmail
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autocomplete={false}
|
||||
bind:value={userEmail} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userEmail === $user.email || !userEmail} submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updatePhone}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">Update Phone</h6>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPhone
|
||||
id="phone"
|
||||
label="Phone"
|
||||
placeholder="Enter phone number"
|
||||
autocomplete={false}
|
||||
bind:value={userPhone} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={userPhone === $user.phone || !userPhone} submit>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updatePassword}>
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Update Password</h6>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Enter a new password. A password must contain <b>at least 8 characters.</b>
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputPassword
|
||||
id="newPassword"
|
||||
label="New Password"
|
||||
placeholder="Enter new password"
|
||||
autocomplete={false}
|
||||
meter={false}
|
||||
showPasswordButton={true}
|
||||
bind:value={newPassword} />
|
||||
</ul>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!newPassword} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<Form on:submit={updatePrefs}>
|
||||
<CardGrid>
|
||||
<h6 class="heading-level-7">User Preferences</h6>
|
||||
<p>
|
||||
You can update your user preferences by storing information on the user's
|
||||
objects so they can easily be shared across devices and sessions.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<form class="form u-grid u-gap-16">
|
||||
<ul class="form-list">
|
||||
{#if prefs}
|
||||
{#each prefs as [key, value], index}
|
||||
<li class="form-item is-multiple">
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`key-${index}`}>Key</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`key-${key}`}
|
||||
placeholder="Enter key"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value={key} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-stretch">
|
||||
<label class="label" for={`value-${index}`}>
|
||||
Value
|
||||
</label>
|
||||
<div class="input-text-wrapper">
|
||||
<input
|
||||
id={`value-${value}`}
|
||||
placeholder="Enter value"
|
||||
type="text"
|
||||
class="input-text"
|
||||
bind:value />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item-part u-cross-child-end">
|
||||
<Button
|
||||
text
|
||||
disabled={(!key || !value) && index === 0}
|
||||
on:click={() => {
|
||||
if (index === 0) {
|
||||
prefs = [['', '']];
|
||||
} else {
|
||||
prefs.splice(index, 1);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
<Button
|
||||
text
|
||||
on:click={() => {
|
||||
if (prefs[prefs.length - 1][0] && prefs[prefs.length - 1][1]) {
|
||||
prefs.push(['', '']);
|
||||
prefs = prefs;
|
||||
}
|
||||
}}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Add Preference</span>
|
||||
</Button>
|
||||
</form>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePrefsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CardGrid>
|
||||
<div>
|
||||
<h6 class="heading-level-7">Danger Zone</h6>
|
||||
</div>
|
||||
<p>
|
||||
The user will be permanently deleted, including all data associated with this user.
|
||||
This action is irreversible.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Box>
|
||||
<svelte:fragment slot="image">
|
||||
<Avatar size={48} name={$user.name} src={getAvatar($user.name)} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<h6 class="u-bold">{$user.name}</h6>
|
||||
</svelte:fragment>
|
||||
<p>{$user.email}</p>
|
||||
</Box>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showDelete = true)}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Container>
|
||||
{/if}
|
||||
<DeleteUser bind:showDelete />
|
||||
+5
-5
@@ -51,14 +51,14 @@
|
||||
<TableBody>
|
||||
{#each response.memberships as membership}
|
||||
<TableRowLink
|
||||
href={`${base}/console/${project}/users/user/${membership.userId}`}>
|
||||
href={`${base}/console/project-${project}/authentication/teams/${membership.teamId}`}>
|
||||
<TableCellText title="Name">
|
||||
<div class="u-flex u-gap-12">
|
||||
<Avatar
|
||||
size={32}
|
||||
src={getAvatar(membership.userName)}
|
||||
name={membership.userName} />
|
||||
<span>{membership.userName ? membership.userName : 'n/a'}</span>
|
||||
src={getAvatar(membership.teamName)}
|
||||
name={membership.teamName} />
|
||||
<span>{membership.teamName ? membership.teamName : 'n/a'}</span>
|
||||
</div>
|
||||
</TableCellText>
|
||||
<TableCellText title="Role">{membership.roles}</TableCellText>
|
||||
@@ -87,7 +87,7 @@
|
||||
<Button
|
||||
external
|
||||
secondary
|
||||
href="https://appwrite.io/docs/server/users?sdk=nodejs-default#usersGetMemberships"
|
||||
href="https://appwrite.io/docs/server/authentication?sdk=nodejs-default#usersGetMemberships"
|
||||
>Documentation</Button>
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user