From 9e53cf29e7b7d309573d8170f2f06ed94df79def Mon Sep 17 00:00:00 2001 From: Arman Date: Fri, 12 Aug 2022 15:44:10 +0200 Subject: [PATCH 01/53] initial commit --- src/lib/layout/header.svelte | 2 +- src/lib/layout/shell.svelte | 47 +++++++-- src/lib/stores/layout.ts | 13 ++- src/routes/console/[project]/__layout.svelte | 10 +- src/routes/console/[project]/store.ts | 4 +- src/routes/console/__layout-intro.svelte | 38 ++++++- src/routes/console/index@intro.svelte | 102 ++++++++++++------- src/routes/console/members@intro.svelte | 5 + src/routes/console/settings@intro.svelte | 5 + src/routes/console/store.ts | 60 +++++++++++ 10 files changed, 227 insertions(+), 59 deletions(-) create mode 100644 src/routes/console/members@intro.svelte create mode 100644 src/routes/console/settings@intro.svelte create mode 100644 src/routes/console/store.ts diff --git a/src/lib/layout/header.svelte b/src/lib/layout/header.svelte index 9cdaaec94..eafe66f91 100644 --- a/src/lib/layout/header.svelte +++ b/src/lib/layout/header.svelte @@ -51,7 +51,7 @@ class:icon-cheveron-down={!showDropdown} /> - New organisation + New organization Your Account diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 97ca79d1d..c82713e93 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -1,18 +1,19 @@ diff --git a/src/routes/console/index@intro.svelte b/src/routes/console/index@intro.svelte index f02af6248..5b115c3b9 100644 --- a/src/routes/console/index@intro.svelte +++ b/src/routes/console/index@intro.svelte @@ -1,67 +1,95 @@ Appwrite - Console -

Your Organizations

- +
+

Projects

- {#await request} -
- {:then organizations} - {#each organizations as organization} -

{organization.name}

- - {#each organization.projects as project} - - {/each} - - - {/each} - {/await} + +
+ + {#if projects?.length} +
    3 ? '22rem' : '25rem'};`}> + {#each projects as project} + + XX apps + + {project.name} + + {#each project.platforms as platform} + + + {/each} + + {/each} + {#if projects.length % 2 !== 0} + (showCreate = true)}> +
    + +
    +
    +

    Add a new bucket

    +
    +
    + {/if} +
+ +
+

Total results: {projects.length}

+ +
+ {/if} diff --git a/src/routes/console/members@intro.svelte b/src/routes/console/members@intro.svelte new file mode 100644 index 000000000..b5ead8b19 --- /dev/null +++ b/src/routes/console/members@intro.svelte @@ -0,0 +1,5 @@ + + Appwrite - Console + + +Members diff --git a/src/routes/console/settings@intro.svelte b/src/routes/console/settings@intro.svelte new file mode 100644 index 000000000..423bdc9e5 --- /dev/null +++ b/src/routes/console/settings@intro.svelte @@ -0,0 +1,5 @@ + + Appwrite - Console + + +settings diff --git a/src/routes/console/store.ts b/src/routes/console/store.ts new file mode 100644 index 000000000..5b90e4a26 --- /dev/null +++ b/src/routes/console/store.ts @@ -0,0 +1,60 @@ +import { sdkForConsole } from '$lib/stores/sdk'; +import type { Models } from '@aw-labs/appwrite-console'; +import { writable } from 'svelte/store'; +import { browser } from '$app/env'; + +function createOrganizationList() { + const { subscribe, set } = writable( + browser ? JSON.parse(sessionStorage.getItem('organizationList')) : null + ); + + return { + subscribe, + set, + load: async () => { + const team = await sdkForConsole.teams.list(); + set(team); + } + }; +} +function createOrganization() { + const { subscribe, set } = writable( + browser ? JSON.parse(sessionStorage.getItem('organization')) : null + ); + + return { + subscribe, + set, + load: async (teamId: string) => { + const team = await sdkForConsole.teams.get(teamId); + set(team); + } + }; +} + +function createProjectList() { + const { subscribe, set } = writable( + browser ? JSON.parse(sessionStorage.getItem('projectList')) : null + ); + + return { + subscribe, + set, + load: async () => { + const projects = await sdkForConsole.projects.list(); + set(projects); + } + }; +} + +export const organizationList = createOrganizationList(); +export const organization = createOrganization(); +export const projectList = createProjectList(); + +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 ?? ''))); +} From ad0c42a6ecc228198d8672b1f93da4fccabf4fcf Mon Sep 17 00:00:00 2001 From: Arman Date: Fri, 12 Aug 2022 17:15:33 +0200 Subject: [PATCH 02/53] feat: members page --- src/lib/layout/shell.svelte | 3 +- src/routes/console/__layout-intro.svelte | 27 ++++-- src/routes/console/index@intro.svelte | 25 +++--- src/routes/console/members@intro.svelte | 100 ++++++++++++++++++++++- src/routes/console/settings@intro.svelte | 4 - src/routes/console/store.ts | 34 ++++++-- 6 files changed, 155 insertions(+), 38 deletions(-) diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index c82713e93..308aaf688 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -4,6 +4,7 @@ import { Cover } from '.'; import { Copy, DropList, DropListItem } from '$lib/components'; import { Pill } from '$lib/elements'; + import { organization } from '../../routes/console/store'; export let isOpen = false; export let showSideNavigation = false; @@ -90,7 +91,7 @@ class="button is-text u-padding-inline-0" on:click={() => (showDropdown = !showDropdown)}>

- drop + {$organization.name} - - -
- - -
- + + Appwrite - Console + + +{#if $organization} + + +
+ + +
+ +{/if} diff --git a/src/routes/console/index@intro.svelte b/src/routes/console/index@intro.svelte index 5b115c3b9..ff24b1d82 100644 --- a/src/routes/console/index@intro.svelte +++ b/src/routes/console/index@intro.svelte @@ -8,17 +8,13 @@ import type { Models } from '@aw-labs/appwrite-console'; import CreateOrganization from './_createOrganization.svelte'; import CreateProject from './_createProject.svelte'; - import { organizationList, organization, projectList } from './store'; + import { organization, projectList } from './store'; import { onMount } from 'svelte'; let projects: Models.Project[] = []; onMount(async () => { - organizationList.load(); - if (!$organization) { - organization.load($organizationList.teams[0].$id); - } - projectList.load(); + await projectList.load(); projects = $projectList.projects.filter((a) => a.teamId === $organization.$id); }); @@ -35,9 +31,6 @@ //TODO: fix technology icon display - - Appwrite - Console -

Projects

@@ -56,7 +49,8 @@ style={`--grid-gap:2rem; --grid-item-size:${projects.length > 3 ? '22rem' : '25rem'};`}> {#each projects as project} - XX apps + {project?.platforms?.length ?? 0} apps {project.name} @@ -89,7 +83,10 @@ - + +{#if projects?.length} + +{/if} diff --git a/src/routes/console/members@intro.svelte b/src/routes/console/members@intro.svelte index b5ead8b19..5409ba64a 100644 --- a/src/routes/console/members@intro.svelte +++ b/src/routes/console/members@intro.svelte @@ -1,5 +1,97 @@ - - Appwrite - Console - + + + + {#if $memberList?.total} +
+

Members

+ + +
+ + + + Name + Email + Role + + + + {#each $memberList.memberships as member} + + +
+ + {member.userName ? member.userName : 'n/a'} + {#if member.invited && !member.joined} + Pending + {/if} +
+
+ {member.userEmail} + + {#if member.invited && !member.joined} + + {:else} + {member.roles} + {/if} + + + + + +
+ {/each} +
+
+
+

Total results: {$memberList.total}

+ +
+ {/if} +
diff --git a/src/routes/console/settings@intro.svelte b/src/routes/console/settings@intro.svelte index 423bdc9e5..a01c11a41 100644 --- a/src/routes/console/settings@intro.svelte +++ b/src/routes/console/settings@intro.svelte @@ -1,5 +1 @@ - - Appwrite - Console - - settings diff --git a/src/routes/console/store.ts b/src/routes/console/store.ts index 5b90e4a26..1afbbef00 100644 --- a/src/routes/console/store.ts +++ b/src/routes/console/store.ts @@ -12,8 +12,8 @@ function createOrganizationList() { subscribe, set, load: async () => { - const team = await sdkForConsole.teams.list(); - set(team); + const response = await sdkForConsole.teams.list(); + set(response); } }; } @@ -26,8 +26,8 @@ function createOrganization() { subscribe, set, load: async (teamId: string) => { - const team = await sdkForConsole.teams.get(teamId); - set(team); + const response = await sdkForConsole.teams.get(teamId); + set(response); } }; } @@ -41,8 +41,28 @@ function createProjectList() { subscribe, set, load: async () => { - const projects = await sdkForConsole.projects.list(); - set(projects); + const response = await sdkForConsole.projects.list(); + set(response); + } + }; +} + +function createMemberList() { + const { subscribe, set } = writable( + 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); } }; } @@ -50,6 +70,7 @@ function createProjectList() { export const organizationList = createOrganizationList(); export const organization = createOrganization(); export const projectList = createProjectList(); +export const memberList = createMemberList(); if (browser) { organizationList.subscribe((n) => @@ -57,4 +78,5 @@ if (browser) { ); 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 ?? ''))); } From 734a51b2694a6fbdac6c189bd116b936fb758a6e Mon Sep 17 00:00:00 2001 From: Arman Date: Mon, 22 Aug 2022 16:45:02 +0200 Subject: [PATCH 03/53] feat: create and delete members --- src/routes/console/_createMember.svelte | 65 +++++++++++++++++++++++++ src/routes/console/_deleteMember.svelte | 50 +++++++++++++++++++ src/routes/console/members@intro.svelte | 21 ++++++-- 3 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 src/routes/console/_createMember.svelte create mode 100644 src/routes/console/_deleteMember.svelte diff --git a/src/routes/console/_createMember.svelte b/src/routes/console/_createMember.svelte new file mode 100644 index 000000000..3b28cb642 --- /dev/null +++ b/src/routes/console/_createMember.svelte @@ -0,0 +1,65 @@ + + +
+ + Invite Member + + + + + + + + + +
diff --git a/src/routes/console/_deleteMember.svelte b/src/routes/console/_deleteMember.svelte new file mode 100644 index 000000000..215cee0a8 --- /dev/null +++ b/src/routes/console/_deleteMember.svelte @@ -0,0 +1,50 @@ + + +
+ + + {isUser ? 'Leave Organization' : 'Delete Member'} + +

+ {isUser + ? `Are you sure you want to leave '${selectedMember.teamName}'?` + : `Are you sure you want to delete ${selectedMember.userName} from '${selectedMember.teamName}'?`} +

+ + + + +
+
diff --git a/src/routes/console/members@intro.svelte b/src/routes/console/members@intro.svelte index 5409ba64a..65aeaed28 100644 --- a/src/routes/console/members@intro.svelte +++ b/src/routes/console/members@intro.svelte @@ -12,20 +12,26 @@ import { Pill } from '$lib/elements'; import { Button } from '$lib/elements/forms'; import { Container } from '$lib/layout'; + import Delete from './_deleteMember.svelte'; + import Create from './_createMember.svelte'; import { organization, memberList } from './store'; import { sdkForProject } from '$lib/stores/sdk'; + import type { Models } from '@aw-labs/appwrite-console'; + import { pageLimit } from '$lib/stores/layout'; - const limit = 5; let search = ''; let offset: number = null; + let selectedMember: Models.Membership; let showCreate = false; let showDelete = false; const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 32, 32).toString(); + const deleted = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0); + const created = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0); $: if (search) offset = 0; - $: memberList.load($organization.$id, search, limit, offset ?? 0); + $: memberList.load($organization.$id, search, $pageLimit, offset ?? 0); @@ -38,7 +44,7 @@ showCreate = true; }}>
@@ -79,7 +85,7 @@ class="button is-only-icon is-text" aria-label="Delete item" on:click|preventDefault={() => { - // selectedMembership = membership; + selectedMember = member; showDelete = true; }}>
+ + created()} /> +{#if selectedMember} + deleted()} /> +{/if} From b8fe4fcc96b5df70330746edd0727c910baf5419 Mon Sep 17 00:00:00 2001 From: Arman Date: Mon, 22 Aug 2022 17:26:39 +0200 Subject: [PATCH 04/53] feat: setting page --- src/routes/console/_deleteOrganization.svelte | 42 +++++++++ src/routes/console/members@intro.svelte | 6 +- src/routes/console/settings@intro.svelte | 89 ++++++++++++++++++- 3 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 src/routes/console/_deleteOrganization.svelte diff --git a/src/routes/console/_deleteOrganization.svelte b/src/routes/console/_deleteOrganization.svelte new file mode 100644 index 000000000..b6050103a --- /dev/null +++ b/src/routes/console/_deleteOrganization.svelte @@ -0,0 +1,42 @@ + + +
+ + Delete Organization +

+ Are you sure you want to delete {$organization.name}? All projects ({$organization.total}) + and data associated with this organization will be deleted. This action is irreversible. +

+ + + + +
+
diff --git a/src/routes/console/members@intro.svelte b/src/routes/console/members@intro.svelte index 65aeaed28..1dbf21e45 100644 --- a/src/routes/console/members@intro.svelte +++ b/src/routes/console/members@intro.svelte @@ -15,7 +15,7 @@ import Delete from './_deleteMember.svelte'; import Create from './_createMember.svelte'; import { organization, memberList } from './store'; - import { sdkForProject } from '$lib/stores/sdk'; + import { sdkForConsole } from '$lib/stores/sdk'; import type { Models } from '@aw-labs/appwrite-console'; import { pageLimit } from '$lib/stores/layout'; @@ -26,7 +26,7 @@ let showCreate = false; let showDelete = false; - const getAvatar = (name: string) => sdkForProject.avatars.getInitials(name, 32, 32).toString(); + const getAvatar = (name: string) => sdkForConsole.avatars.getInitials(name, 32, 32).toString(); const deleted = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0); const created = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0); @@ -62,7 +62,7 @@
{member.userName ? member.userName : 'n/a'} diff --git a/src/routes/console/settings@intro.svelte b/src/routes/console/settings@intro.svelte index a01c11a41..5ef8ef366 100644 --- a/src/routes/console/settings@intro.svelte +++ b/src/routes/console/settings@intro.svelte @@ -1 +1,88 @@ -settings + + + +
+ +
Update Name
+ + +
    + +
+
+ + + + +
+
+ + +
+
Delete Organization
+
+

+ The organization will be permanently deleted, including all projects and data associated + with this organization. This action is irreversible. +

+ + + + + + +
{$organization.name}
+
+

{$organization.total} projects

+
+
+ + + + +
+
+ + From 9dec0ef2db4cc67b280068ba8961ba9b292ae2f7 Mon Sep 17 00:00:00 2001 From: Arman Date: Mon, 22 Aug 2022 17:44:28 +0200 Subject: [PATCH 05/53] feat: create org & projects modals --- src/routes/console/_createOrganization.svelte | 64 ++++++++++++++++--- src/routes/console/_createProject.svelte | 62 +++++++++++++++--- 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/src/routes/console/_createOrganization.svelte b/src/routes/console/_createOrganization.svelte index 787fab444..a5967732d 100644 --- a/src/routes/console/_createOrganization.svelte +++ b/src/routes/console/_createOrganization.svelte @@ -1,6 +1,7 @@
- - Create Project - + + Create New Organization + + + {#if !showDropdown} +
+ (showDropdown = !showDropdown)}> + +
+ {:else} + + Organization ID + + Enter a custom organization ID. Leave blank for a randomly generated one. + + +
+ + +
+
+
+
+
+ {/if} +
- +
diff --git a/src/routes/console/_createProject.svelte b/src/routes/console/_createProject.svelte index 20a9f521b..fbf906e07 100644 --- a/src/routes/console/_createProject.svelte +++ b/src/routes/console/_createProject.svelte @@ -1,6 +1,7 @@
- + Create Project - - + + + {#if !showDropdown} +
+ (showDropdown = !showDropdown)}> + +
+ {:else} + + Project ID + + Enter a custom project ID. Leave blank for a randomly generated one. + + +
+ + +
+
+
+
+
+ {/if} +
- +
From 9d4ae0329acd90d20198401dbceee955ece66e89 Mon Sep 17 00:00:00 2001 From: Arman Date: Tue, 23 Aug 2022 14:27:08 +0200 Subject: [PATCH 06/53] feat: implemented most of the pages --- src/lib/layout/shell.svelte | 30 ++++++++++---- src/routes/console/__layout-intro.svelte | 15 ++++++- src/routes/console/__layout.svelte | 3 +- src/routes/console/_deleteOrganization.svelte | 5 ++- src/routes/console/index@intro.svelte | 39 +++++++++++++++---- src/routes/console/members@intro.svelte | 8 +--- src/routes/console/settings@intro.svelte | 4 +- src/routes/console/store.ts | 2 + 8 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 0499ef6de..024c5e6e7 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -4,13 +4,12 @@ import { Cover } from '.'; import { Copy, DropList, DropListItem } from '$lib/components'; import { Pill } from '$lib/elements'; - import { organization } from '../../routes/console/store'; + import { Button } from '$lib/elements/forms'; + import { organization, newOrgModal, newMemberModal } from '../../routes/console/store'; export let isOpen = false; export let showSideNavigation = false; - // $: base = `/console/${$page.params.project}`; - let tabsList: HTMLUListElement; let showLeft = false; let showRight = false; @@ -99,19 +98,36 @@

- {#each $titleDropdown as organization} - - {organization.name} + {#each $titleDropdown as org} + { + showDropdown = false; + if (org.$id !== $organization.$id) { + organization.load(org.$id); + } + }}> + {org.name} {/each}
    - New Organizations + { + showDropdown = false; + newOrgModal.set(true); + }}>New Organization
+
+ +
{:else}

{$title} diff --git a/src/routes/console/__layout-intro.svelte b/src/routes/console/__layout-intro.svelte index c388793ee..285ae7f1b 100644 --- a/src/routes/console/__layout-intro.svelte +++ b/src/routes/console/__layout-intro.svelte @@ -4,7 +4,11 @@ import { updateLayout } from '$lib/stores/layout'; import { onMount } from 'svelte'; import { afterNavigate } from '$app/navigation'; - import { organizationList, organization } from './store'; + import { organizationList, organization, newOrgModal, newMemberModal } from './store'; + import CreateMember from './_createMember.svelte'; + import Create from './_createOrganization.svelte'; + + const created = () => console.log('test'); onMount(handle); afterNavigate(handle); @@ -16,7 +20,7 @@ } updateLayout({ navigate: event, - title: `${$organizationList.teams[0].name}`, + title: $organization?.name ?? `${$organizationList.teams[0].name}`, titleDropdown: $organizationList.teams, level: 0, customBase: '', @@ -40,6 +44,10 @@ ] }); } + + organization.subscribe(() => { + handle(); + }); @@ -55,3 +63,6 @@
{/if} + + created()} /> + created()} /> diff --git a/src/routes/console/__layout.svelte b/src/routes/console/__layout.svelte index 340e96d0d..929e21596 100644 --- a/src/routes/console/__layout.svelte +++ b/src/routes/console/__layout.svelte @@ -3,9 +3,10 @@ import SideNavigation from '$lib/layout/navigation.svelte'; import Header from '$lib/layout/header.svelte'; import { updateLayout } from '$lib/stores/layout'; + import { organization } from './store'; updateLayout({ - title: 'Projects', + title: $organization.name ?? 'Projects', level: 0 }); diff --git a/src/routes/console/_deleteOrganization.svelte b/src/routes/console/_deleteOrganization.svelte index b6050103a..edd51b37f 100644 --- a/src/routes/console/_deleteOrganization.svelte +++ b/src/routes/console/_deleteOrganization.svelte @@ -5,7 +5,7 @@ import { Button, Form } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { sdkForConsole } from '$lib/stores/sdk'; - import { organization } from './store'; + import { organization, organizationList } from './store'; export let showDelete = false; @@ -17,6 +17,9 @@ type: 'success', message: `${$organization.name} has been deleted` }); + if ($organizationList?.total) { + organization.set($organizationList.teams[0]); + } await goto(`${base}/console/`); } catch (error) { addNotification({ diff --git a/src/routes/console/index@intro.svelte b/src/routes/console/index@intro.svelte index ff24b1d82..63c62e6d6 100644 --- a/src/routes/console/index@intro.svelte +++ b/src/routes/console/index@intro.svelte @@ -2,7 +2,7 @@ import { goto } from '$app/navigation'; import { base } from '$app/paths'; import { Pill } from '$lib/elements'; - import { Bucket, EmptyBucket, Pagination } from '$lib/components'; + 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'; @@ -28,6 +28,12 @@ const projectCreated = async (event: CustomEvent) => { await goto(`${base}/console/${event.detail.$id}`); }; + + $: if (currentOrganization !== $organization?.$id) { + currentOrganization = $organization.$id; + projectList.load(); + projects = $projectList.projects.filter((a) => a.teamId === $organization.$id); + } //TODO: fix technology icon display @@ -69,12 +75,31 @@

-

Add a new bucket

+

Create a new project

{/if} +
+

Total results: {projects.length}

+ +
+ {:else} + +
+
+
+ +
+
+

Create a new project

+
+
+
+

Total results: {projects.length}

@@ -84,9 +109,7 @@ -{#if projects?.length} - -{/if} + diff --git a/src/routes/console/members@intro.svelte b/src/routes/console/members@intro.svelte index 1dbf21e45..3c4c67a56 100644 --- a/src/routes/console/members@intro.svelte +++ b/src/routes/console/members@intro.svelte @@ -13,8 +13,7 @@ import { Button } from '$lib/elements/forms'; import { Container } from '$lib/layout'; import Delete from './_deleteMember.svelte'; - import Create from './_createMember.svelte'; - import { organization, memberList } from './store'; + import { organization, memberList, newMemberModal } from './store'; import { sdkForConsole } from '$lib/stores/sdk'; import type { Models } from '@aw-labs/appwrite-console'; import { pageLimit } from '$lib/stores/layout'; @@ -23,12 +22,10 @@ let offset: number = null; let selectedMember: Models.Membership; - let showCreate = false; let showDelete = false; const getAvatar = (name: string) => sdkForConsole.avatars.getInitials(name, 32, 32).toString(); const deleted = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0); - const created = () => memberList.load($organization.$id, search, $pageLimit, offset ?? 0); $: if (search) offset = 0; $: memberList.load($organization.$id, search, $pageLimit, offset ?? 0); @@ -41,7 +38,7 @@ + {:else} {member.roles} {/if} @@ -81,7 +92,8 @@