|
1 | 1 | <script lang="ts"> |
2 | | - import { Button } from '$lib/elements/forms'; |
3 | | - import { DropList, GridItem1, CardContainer } from '$lib/components'; |
4 | 2 | import ArchivedPaginationWithLimit from './archivedPaginationWithLimit.svelte'; |
| 3 | + import { Button, InputText } from '$lib/elements/forms'; |
| 4 | + import { DropList, GridItem1, CardContainer, Modal } from '$lib/components'; |
| 5 | + import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; |
5 | 6 | import { |
6 | 7 | Badge, |
7 | 8 | Icon, |
|
10 | 11 | Accordion, |
11 | 12 | ActionMenu, |
12 | 13 | Popover, |
13 | | - Layout |
| 14 | + Layout, |
| 15 | + Divider |
14 | 16 | } from '@appwrite.io/pink-svelte'; |
15 | 17 | import { |
16 | 18 | IconAndroid, |
|
22 | 24 | IconInfo, |
23 | 25 | IconDotsHorizontal, |
24 | 26 | IconInboxIn, |
25 | | - IconSwitchHorizontal |
| 27 | + IconSwitchHorizontal, |
| 28 | + IconTrash |
26 | 29 | } from '@appwrite.io/pink-icons-svelte'; |
27 | 30 | import { getPlatformInfo } from '$lib/helpers/platform'; |
28 | 31 | import { Status, type Models } from '@appwrite.io/console'; |
|
34 | 37 | import { addNotification } from '$lib/stores/notifications'; |
35 | 38 | import { invalidate } from '$app/navigation'; |
36 | 39 | import { Dependencies } from '$lib/constants'; |
37 | | - import { Modal } from '$lib/components'; |
| 40 | +
|
38 | 41 | import { isSmallViewport } from '$lib/stores/viewport'; |
39 | 42 | import { isCloud } from '$lib/system'; |
40 | 43 | import { regions as regionsStore } from '$lib/stores/organization'; |
|
64 | 67 | let readOnlyInfoOpen = $state<Record<string, boolean>>({}); |
65 | 68 | let showUnarchiveModal = $state(false); |
66 | 69 | let projectToUnarchive = $state<Models.Project | null>(null); |
| 70 | + let showDeleteModal = $state(false); |
| 71 | + let projectToDelete = $state<Models.Project | null>(null); |
| 72 | + let deleteProjectName = $state<string | null>(null); |
| 73 | + let deleteError = $state<string | null>(null); |
| 74 | +
|
| 75 | + function resetDeleteState() { |
| 76 | + showDeleteModal = false; |
| 77 | + projectToDelete = null; |
| 78 | + deleteProjectName = null; |
| 79 | + deleteError = null; |
| 80 | + } |
67 | 81 |
|
68 | 82 | function filterPlatforms(platforms: { name: string; icon: string }[]) { |
69 | 83 | return platforms.filter( |
|
114 | 128 | showUnarchiveModal = true; |
115 | 129 | } |
116 | 130 |
|
| 131 | + function handleDeleteProject(project: Models.Project) { |
| 132 | + projectToDelete = project; |
| 133 | + deleteProjectName = null; |
| 134 | + showDeleteModal = true; |
| 135 | + } |
| 136 | +
|
117 | 137 | // Confirm unarchive action |
118 | 138 | async function confirmUnarchive() { |
119 | 139 | if (!projectToUnarchive) return; |
|
152 | 172 | projectToUnarchive = null; |
153 | 173 | } |
154 | 174 |
|
| 175 | + async function confirmDelete() { |
| 176 | + if (!projectToDelete) return; |
| 177 | +
|
| 178 | + try { |
| 179 | + await sdk.forConsoleIn(projectToDelete.region).projects.delete({ |
| 180 | + projectId: projectToDelete.$id |
| 181 | + }); |
| 182 | +
|
| 183 | + await invalidate(Dependencies.ORGANIZATION); |
| 184 | +
|
| 185 | + trackEvent(Submit.ProjectDelete); |
| 186 | + addNotification({ |
| 187 | + type: 'success', |
| 188 | + message: `${projectToDelete.name} has been deleted` |
| 189 | + }); |
| 190 | +
|
| 191 | + resetDeleteState(); |
| 192 | + } catch (error) { |
| 193 | + deleteError = error.message; |
| 194 | + trackError(error, Submit.ProjectDelete); |
| 195 | + } |
| 196 | + } |
| 197 | +
|
155 | 198 | function findRegion(project: Models.Project) { |
156 | 199 | return $regionsStore.regions.find((region) => region.$id === project.region); |
157 | 200 | } |
|
237 | 280 | leadingIcon={IconSwitchHorizontal} |
238 | 281 | on:click={() => handleMigrateProject(project)} |
239 | 282 | >Migrate project</ActionMenu.Item.Button> |
| 283 | + <div class="action-menu-divider"> |
| 284 | + <Divider /> |
| 285 | + </div> |
| 286 | + <ActionMenu.Item.Button |
| 287 | + status="danger" |
| 288 | + leadingIcon={IconTrash} |
| 289 | + on:click={() => handleDeleteProject(project)} |
| 290 | + >Delete project</ActionMenu.Item.Button> |
240 | 291 | </ActionMenu.Root> |
241 | 292 | </Popover> |
242 | 293 | </div> |
|
294 | 345 | </svelte:fragment> |
295 | 346 | </Modal> |
296 | 347 |
|
| 348 | +<!-- Delete Confirmation Modal --> |
| 349 | +<Modal |
| 350 | + size="s" |
| 351 | + bind:show={showDeleteModal} |
| 352 | + title="Delete project" |
| 353 | + onSubmit={confirmDelete} |
| 354 | + bind:error={deleteError}> |
| 355 | + <svelte:fragment slot="description"> |
| 356 | + The archived project <strong>{projectToDelete?.name}</strong> will be deleted along with all |
| 357 | + of its metadata, stats, and other resources. |
| 358 | + <b>This action is irreversible.</b> |
| 359 | + </svelte:fragment> |
| 360 | + |
| 361 | + <InputText |
| 362 | + label={`Enter "${projectToDelete?.name}" to continue`} |
| 363 | + placeholder="Enter name" |
| 364 | + id="delete-project-name" |
| 365 | + autofocus |
| 366 | + required |
| 367 | + bind:value={deleteProjectName} /> |
| 368 | + |
| 369 | + <svelte:fragment slot="footer"> |
| 370 | + <Button |
| 371 | + text |
| 372 | + on:click={() => { |
| 373 | + resetDeleteState(); |
| 374 | + }}>Cancel</Button> |
| 375 | + <Button |
| 376 | + submissionLoader |
| 377 | + submit |
| 378 | + disabled={(deleteProjectName ?? '') !== projectToDelete?.name}> |
| 379 | + Delete |
| 380 | + </Button> |
| 381 | + </svelte:fragment> |
| 382 | +</Modal> |
| 383 | + |
297 | 384 | <style> |
298 | 385 | .archive-projects-margin-top { |
299 | 386 | margin-top: 36px; |
300 | 387 | } |
| 388 | + .action-menu-divider { |
| 389 | + margin-inline: -1rem; |
| 390 | + padding-block-start: 0.25rem; |
| 391 | + padding-block-end: 0.25rem; |
| 392 | + } |
301 | 393 |
|
302 | 394 | .archive-projects-margin { |
303 | 395 | margin-top: 16px; |
|
0 commit comments