diff --git a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte index 1f2dc4423..80da55e02 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte @@ -20,13 +20,26 @@ import type { Models } from '@appwrite.io/console'; import { writable } from 'svelte/store'; import Create from './createUser.svelte'; - import { Badge, Icon, Table, Layout, Typography } from '@appwrite.io/pink-svelte'; + import { + Badge, + Icon, + Table, + Layout, + Typography, + FloatingActionBar + } from '@appwrite.io/pink-svelte'; import { Tag } from '@appwrite.io/pink-svelte'; import { IconDuplicate, IconPlus } from '@appwrite.io/pink-icons-svelte'; import { canWriteUsers } from '$lib/stores/roles'; import type { Column } from '$lib/helpers/types'; import ViewSelector from '$lib/components/viewSelector.svelte'; import { View } from '$lib/helpers/load'; + import { sdk } from '$lib/stores/sdk'; + import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; + import { Dependencies } from '$lib/constants'; + import { addNotification } from '$lib/stores/notifications'; + import { invalidate } from '$app/navigation'; + import Confirm from '$lib/components/confirm.svelte'; export let data; @@ -46,11 +59,47 @@ } ]); + let selectedUsers: string[] = []; + let showDelete = false; + let deleting = false; + async function userCreated(event: CustomEvent>>) { + showCreateUser.set(false); await goto( `${base}/project-${page.params.region}-${page.params.project}/auth/user-${event.detail.$id}` ); } + + async function handleDelete() { + showDelete = false; + deleting = true; + + const promises = selectedUsers.map((userId) => + sdk.forProject(page.params.region, page.params.project).users.delete(userId) + ); + + try { + await Promise.all(promises); + trackEvent(Submit.UserDelete, { + total: selectedUsers.length + }); + addNotification({ + type: 'success', + message: `${selectedUsers.length} user${selectedUsers.length > 1 ? 's' : ''} deleted` + }); + invalidate(Dependencies.USERS); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.UserDelete); + } finally { + selectedUsers = []; + showDelete = false; + deleting = false; + } + } @@ -68,7 +117,11 @@ {#if data.users.total} - + {#each $columns as { id, title } (id)} {title} @@ -77,7 +130,8 @@ {#each data.users.users as user} + {root} + id={user.$id}> {#each $columns as { id } (id)} {#if id === '$id'} @@ -175,6 +229,33 @@ allowCreate={$canWriteUsers} on:click={() => showCreateUser.set(true)} /> {/if} + + {#if selectedUsers.length > 0} + + + + + {selectedUsers.length > 1 ? 'users' : 'user'} + selected + + + + + + + + {/if} + + + + Are you sure you want to delete {selectedUsers.length} + {selectedUsers.length > 1 ? 'users' : 'user'}? + + + This action is irreversible and will permanently remove the selected users and all their + data. + + diff --git a/src/routes/(console)/project-[region]-[project]/auth/+page.ts b/src/routes/(console)/project-[region]-[project]/auth/+page.ts index c92055259..1453b72dd 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/+page.ts @@ -1,11 +1,13 @@ import { Query } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load'; -import { PAGE_LIMIT } from '$lib/constants'; +import { PAGE_LIMIT, Dependencies } from '$lib/constants'; import type { PageLoad } from './$types'; import { showCreateUser } from './+page.svelte'; -export const load: PageLoad = async ({ url, route, params }) => { +export const load: PageLoad = async ({ url, route, params, depends }) => { + depends(Dependencies.USERS); + const page = getPage(url); const search = getSearch(url); const limit = getLimit(url, route, PAGE_LIMIT); diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte index cbefb02fb..3a64c2500 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte @@ -19,17 +19,72 @@ import type { Models } from '@appwrite.io/console'; import { writable } from 'svelte/store'; import { canWriteTeams } from '$lib/stores/roles'; - import { Icon, Layout, Table } from '@appwrite.io/pink-svelte'; + import { + Icon, + Layout, + Table, + FloatingActionBar, + Badge, + Typography + } from '@appwrite.io/pink-svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import DualTimeView from '$lib/components/dualTimeView.svelte'; + import { sdk } from '$lib/stores/sdk'; + import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; + import { Dependencies } from '$lib/constants'; + import { addNotification } from '$lib/stores/notifications'; + import { invalidate } from '$app/navigation'; + import Confirm from '$lib/components/confirm.svelte'; export let data; const region = page.params.region; const project = page.params.project; + + let selectedTeams: string[] = []; + let showDelete = false; + let deleting = false; + + const columns = writable([ + { id: 'name', title: 'Name', type: 'string', width: { min: 200, max: 300 } }, + { id: 'members', title: 'Members', type: 'string', width: { min: 120, max: 150 } }, + { id: 'created', title: 'Created', type: 'string', width: { min: 120, max: 180 } } + ]); + const teamCreated = async (event: CustomEvent>>) => { await goto(`${base}/project-${region}-${project}/auth/teams/team-${event.detail.$id}`); }; + + async function handleDelete() { + showDelete = false; + deleting = true; + + const promises = selectedTeams.map((teamId) => + sdk.forProject(page.params.region, page.params.project).teams.delete(teamId) + ); + + try { + await Promise.all(promises); + trackEvent(Submit.TeamDelete, { + total: selectedTeams.length + }); + addNotification({ + type: 'success', + message: `${selectedTeams.length} team${selectedTeams.length > 1 ? 's' : ''} deleted` + }); + invalidate(Dependencies.TEAMS); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.TeamDelete); + } finally { + selectedTeams = []; + showDelete = false; + deleting = false; + } + } @@ -46,32 +101,55 @@ {#if data.teams.total} - + - Name - Members - Created + {#each $columns as { id, title }} + {title} + {/each} - {#each data.teams.teams as team} + {#each data.teams.teams as team (team.$id)} - - - - {team.name} - - - - {team.total} members - - - - + href={`${base}/project-${region}-${project}/auth/teams/team-${team.$id}`} + id={team.$id}> + {#each $columns as column} + + {#if column.id === 'name'} + + + {team.name} + + {:else if column.id === 'members'} + {team.total} members + {:else if column.id === 'created'} + + {/if} + + {/each} {/each} + {#if selectedTeams.length > 0} + + + + + {selectedTeams.length > 1 ? 'teams' : 'team'} + selected + + + + + + + + {/if} + + + + + Are you sure you want to delete {selectedTeams.length} + {selectedTeams.length > 1 ? 'teams' : 'team'}? + + + This action is irreversible and will permanently remove the selected teams and all their + memberships. + + diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.ts b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.ts index 5f3bd1418..2a635068c 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.ts @@ -1,10 +1,12 @@ import { Query } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load'; -import { PAGE_LIMIT } from '$lib/constants'; +import { PAGE_LIMIT, Dependencies } from '$lib/constants'; import type { PageLoad } from './$types'; -export const load: PageLoad = async ({ url, route, params }) => { +export const load: PageLoad = async ({ url, route, params, depends }) => { + depends(Dependencies.TEAMS); + const page = getPage(url); const search = getSearch(url); const limit = getLimit(url, route, PAGE_LIMIT); diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte index a7dba1d1f..00d33151b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte @@ -11,15 +11,28 @@ import CreateMember from '../createMembership.svelte'; import DeleteMembership from '../deleteMembership.svelte'; import { Dependencies } from '$lib/constants'; - import { Click, trackEvent } from '$lib/actions/analytics'; - import { Table, Layout, Icon } from '@appwrite.io/pink-svelte'; + import { Click, trackEvent, Submit, trackError } from '$lib/actions/analytics'; + import { + Table, + Layout, + Icon, + FloatingActionBar, + Badge, + Typography + } from '@appwrite.io/pink-svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; + import { sdk } from '$lib/stores/sdk'; + import { addNotification } from '$lib/stores/notifications'; + import Confirm from '$lib/components/confirm.svelte'; export let data: PageData; let showCreate = false; let showDelete = false; let selectedMembership: Models.Membership; + let selectedMemberships: string[] = []; + let showBulkDelete = false; + let deleting = false; const region = page.params.region; const project = page.params.project; @@ -27,6 +40,40 @@ async function memberCreated() { invalidate(Dependencies.MEMBERSHIPS); } + + async function handleBulkDelete() { + showBulkDelete = false; + deleting = true; + + const promises = selectedMemberships.map((membershipId) => + sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({ + teamId: page.params.team, + membershipId + }) + ); + + try { + await Promise.all(promises); + trackEvent(Submit.MembershipUpdate, { + total: selectedMemberships.length + }); + addNotification({ + type: 'success', + message: `${selectedMemberships.length} membership${selectedMemberships.length > 1 ? 's' : ''} deleted` + }); + invalidate(Dependencies.MEMBERSHIPS); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.MembershipUpdate); + } finally { + selectedMemberships = []; + showBulkDelete = false; + deleting = false; + } + } @@ -40,6 +87,8 @@ {#if data.memberships.total} Joined - {#each data.memberships.memberships as membership} + {#each data.memberships.memberships as membership (membership.$id)} {@const username = membership.userName ? membership.userName : '-'} + href={`${base}/project-${region}-${project}/auth/user-${membership.userId}`} + id={membership.$id}> @@ -85,6 +135,22 @@ {/each} + {#if selectedMemberships.length > 0} + + + + + {selectedMemberships.length > 1 ? 'memberships' : 'membership'} + selected + + + + + + + + {/if} +
- Sorry, we couldn't find ‘{data.search}' + Sorry, we couldn't find '{data.search}'

There are no members that match your search.

+ + + + {/if}
+ + + + Are you sure you want to delete {selectedMemberships.length} + {selectedMemberships.length > 1 ? 'memberships' : 'membership'}? + + + This action is irreversible and will remove the user from the selected teams. + + diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.ts b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.ts index a1be3c971..ef56d144a 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.ts @@ -1,7 +1,10 @@ import { sdk } from '$lib/stores/sdk'; +import { Dependencies } from '$lib/constants'; import type { PageLoad } from './$types'; -export const load: PageLoad = async ({ params }) => { +export const load: PageLoad = async ({ params, depends }) => { + depends(Dependencies.MEMBERSHIPS); + const codes = await sdk.forProject(params.region, params.project).locale.listCodes(); return { localeCodes: codes.localeCodes, diff --git a/src/routes/(console)/project-[region]-[project]/storage/+page.ts b/src/routes/(console)/project-[region]-[project]/storage/+page.ts index f83547708..06d0ff470 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/storage/+page.ts @@ -1,10 +1,12 @@ import { Query } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { getLimit, getPage, getSearch, getView, pageToOffset, View } from '$lib/helpers/load'; -import { CARD_LIMIT } from '$lib/constants'; +import { CARD_LIMIT, Dependencies } from '$lib/constants'; import type { PageLoad } from './$types'; -export const load: PageLoad = async ({ url, route, params }) => { +export const load: PageLoad = async ({ url, route, params, depends }) => { + depends(Dependencies.BUCKET); + const page = getPage(url); const search = getSearch(url); const view = getView(url, route, View.Grid); diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte index 5aa634cf5..2d8524fc4 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte @@ -14,7 +14,17 @@ import { uploader } from '$lib/stores/uploader'; import { sdk } from '$lib/stores/sdk.js'; import DeleteFile from './deleteFile.svelte'; - import { Layout, Table, Icon, Popover, ActionMenu } from '@appwrite.io/pink-svelte'; + import { bucket } from './store'; + import Confirm from '$lib/components/confirm.svelte'; + import { + Layout, + Table, + Icon, + Popover, + ActionMenu, + FloatingActionBar, + Typography + } from '@appwrite.io/pink-svelte'; import { onMount } from 'svelte'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { @@ -28,6 +38,9 @@ let showDelete = false; let selectedFile: Models.File = null; + let selectedFiles: string[] = []; + let showBulkDelete = false; + let deleting = false; const bucketId = page.params.bucket; @@ -52,19 +65,41 @@ } async function deleteFile(file: Models.File) { + selectedFile = file; + showDelete = true; + } + + async function handleBulkDelete() { + showBulkDelete = false; + deleting = true; + + const promises = selectedFiles.map((fileId) => + sdk.forProject(page.params.region, page.params.project).storage.deleteFile({ + bucketId, + fileId + }) + ); + try { - await sdk - .forProject(page.params.region, page.params.project) - .storage.deleteFile({ bucketId: file.bucketId, fileId: file.$id }); - await invalidate(Dependencies.FILES); - await uploader.removeFile(file); - trackEvent(Submit.FileDelete); + await Promise.all(promises); + trackEvent(Submit.FileDelete, { + total: selectedFiles.length + }); + addNotification({ + type: 'success', + message: `${selectedFiles.length} file${selectedFiles.length > 1 ? 's' : ''} deleted` + }); + invalidate(Dependencies.FILES); } catch (error) { addNotification({ type: 'error', message: error.message }); trackError(error, Submit.FileDelete); + } finally { + selectedFiles = []; + showBulkDelete = false; + deleting = false; } } @@ -112,6 +147,8 @@ {#if data.files.total} {#each data.files.files as file} {#if file.chunksTotal / file.chunksUploaded !== 1} - + @@ -158,7 +195,7 @@ {:else} {@const href = `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${bucketId}/file-${file.$id}`} - +
@@ -213,37 +250,55 @@ offset={data.offset} total={data.files.total} /> {:else if data.search} - -
- Sorry, we couldn't find '{data.search}' -

There are no files that match your search.

-
-
- - -
+ + {:else} goto( - `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${page.params.bucket}/create` + `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${bucketId}/create` )} /> {/if} + + {#if selectedFiles.length > 0} + + + + + {selectedFiles.length > 1 ? 'files' : 'file'} + selected + + + + + + + + {/if} -{#if selectedFile} - -{/if} + + + + + Are you sure you want to delete {selectedFiles.length} + {selectedFiles.length > 1 ? 'files' : 'file'} from {$bucket?.name}? + + + This action is irreversible and will permanently remove the selected files. + + diff --git a/src/routes/(console)/project-[region]-[project]/storage/table.svelte b/src/routes/(console)/project-[region]-[project]/storage/table.svelte index accff4d00..3ea8e4b0b 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/table.svelte @@ -5,12 +5,59 @@ import { toLocaleDateTime } from '$lib/helpers/date'; import type { PageData } from './$types'; import { columns } from './store'; - import { Table } from '@appwrite.io/pink-svelte'; + import { Table, FloatingActionBar, Badge, Typography } from '@appwrite.io/pink-svelte'; + import { Button } from '$lib/elements/forms'; + import { sdk } from '$lib/stores/sdk'; + import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; + import { Dependencies } from '$lib/constants'; + import { addNotification } from '$lib/stores/notifications'; + import { invalidate } from '$app/navigation'; + import Confirm from '$lib/components/confirm.svelte'; + import { canWriteBuckets } from '$lib/stores/roles'; export let data: PageData; + + let selectedBuckets: string[] = []; + let showDelete = false; + let deleting = false; + + async function handleDelete() { + showDelete = false; + deleting = true; + + const promises = selectedBuckets.map((bucketId) => + sdk.forProject(page.params.region, page.params.project).storage.deleteBucket(bucketId) + ); + + try { + await Promise.all(promises); + trackEvent(Submit.BucketDelete, { + total: selectedBuckets.length + }); + addNotification({ + type: 'success', + message: `${selectedBuckets.length} bucket${selectedBuckets.length > 1 ? 's' : ''} deleted` + }); + invalidate(Dependencies.BUCKET); + } catch (error) { + addNotification({ + type: 'error', + message: error.message + }); + trackError(error, Submit.BucketDelete); + } finally { + selectedBuckets = []; + showDelete = false; + deleting = false; + } + } - + {#each $columns as { id, title }} {title} @@ -19,7 +66,8 @@ {#each data.buckets.buckets as bucket (bucket.$id)} + href={`${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${bucket.$id}`} + id={bucket.$id}> {#each $columns as column} {#if column.id === '$id'} @@ -38,3 +86,30 @@ {/each} + +{#if selectedBuckets.length > 0} + + + + + {selectedBuckets.length > 1 ? 'buckets' : 'bucket'} + selected + + + + + + + +{/if} + + + + Are you sure you want to delete {selectedBuckets.length} + {selectedBuckets.length > 1 ? 'buckets' : 'bucket'}? + + + This action is irreversible and will permanently remove the selected buckets and all their + files. + +