|
1 | 1 | <script lang="ts"> |
2 | | - import { Button } from '$lib/elements/forms'; |
3 | | - import { DropList, GridItem1, CardContainer } from '$lib/components'; |
| 2 | + import { Button, InputText } from '$lib/elements/forms'; |
| 3 | + import { DropList, GridItem1, CardContainer, Modal } from '$lib/components'; |
| 4 | + import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; |
4 | 5 | import { |
5 | 6 | Badge, |
6 | 7 | Icon, |
|
9 | 10 | Accordion, |
10 | 11 | ActionMenu, |
11 | 12 | Popover, |
12 | | - Layout |
| 13 | + Layout, |
| 14 | + Divider |
13 | 15 | } from '@appwrite.io/pink-svelte'; |
14 | 16 | import { |
15 | 17 | IconAndroid, |
|
21 | 23 | IconInfo, |
22 | 24 | IconDotsHorizontal, |
23 | 25 | IconInboxIn, |
24 | | - IconSwitchHorizontal |
| 26 | + IconSwitchHorizontal, |
| 27 | + IconTrash |
25 | 28 | } from '@appwrite.io/pink-icons-svelte'; |
26 | 29 | import { getPlatformInfo } from '$lib/helpers/platform'; |
27 | 30 | import { Status, type Models } from '@appwrite.io/console'; |
|
33 | 36 | import { addNotification } from '$lib/stores/notifications'; |
34 | 37 | import { invalidate } from '$app/navigation'; |
35 | 38 | import { Dependencies } from '$lib/constants'; |
36 | | - import { Modal } from '$lib/components'; |
| 39 | +
|
37 | 40 | import { isSmallViewport } from '$lib/stores/viewport'; |
38 | 41 | import { isCloud } from '$lib/system'; |
39 | 42 | import { regions as regionsStore } from '$lib/stores/organization'; |
|
53 | 56 | let readOnlyInfoOpen = $state<Record<string, boolean>>({}); |
54 | 57 | let showUnarchiveModal = $state(false); |
55 | 58 | let projectToUnarchive = $state<Models.Project | null>(null); |
| 59 | + let showDeleteModal = $state(false); |
| 60 | + let projectToDelete = $state<Models.Project | null>(null); |
| 61 | + let deleteProjectName = $state<string | null>(null); |
| 62 | + let deleteError = $state<string | null>(null); |
| 63 | +
|
| 64 | + function resetDeleteState() { |
| 65 | + showDeleteModal = false; |
| 66 | + projectToDelete = null; |
| 67 | + deleteProjectName = null; |
| 68 | + deleteError = null; |
| 69 | + } |
56 | 70 |
|
57 | 71 | function filterPlatforms(platforms: { name: string; icon: string }[]) { |
58 | 72 | return platforms.filter( |
|
103 | 117 | showUnarchiveModal = true; |
104 | 118 | } |
105 | 119 |
|
| 120 | + function handleDeleteProject(project: Models.Project) { |
| 121 | + projectToDelete = project; |
| 122 | + deleteProjectName = null; |
| 123 | + showDeleteModal = true; |
| 124 | + } |
| 125 | +
|
106 | 126 | // Confirm unarchive action |
107 | 127 | async function confirmUnarchive() { |
108 | 128 | if (!projectToUnarchive) return; |
|
141 | 161 | projectToUnarchive = null; |
142 | 162 | } |
143 | 163 |
|
| 164 | + async function confirmDelete() { |
| 165 | + if (!projectToDelete) return; |
| 166 | +
|
| 167 | + try { |
| 168 | + await sdk.forConsoleIn(projectToDelete.region).projects.delete({ |
| 169 | + projectId: projectToDelete.$id |
| 170 | + }); |
| 171 | +
|
| 172 | + await invalidate(Dependencies.ORGANIZATION); |
| 173 | +
|
| 174 | + trackEvent(Submit.ProjectDelete); |
| 175 | + addNotification({ |
| 176 | + type: 'success', |
| 177 | + message: `${projectToDelete.name} has been deleted` |
| 178 | + }); |
| 179 | +
|
| 180 | + resetDeleteState(); |
| 181 | + } catch (error) { |
| 182 | + deleteError = error.message; |
| 183 | + trackError(error, Submit.ProjectDelete); |
| 184 | + } |
| 185 | + } |
| 186 | +
|
144 | 187 | function findRegion(project: Models.Project) { |
145 | 188 | return $regionsStore.regions.find((region) => region.$id === project.region); |
146 | 189 | } |
|
226 | 269 | leadingIcon={IconSwitchHorizontal} |
227 | 270 | on:click={() => handleMigrateProject(project)} |
228 | 271 | >Migrate project</ActionMenu.Item.Button> |
| 272 | + <div class="action-menu-divider"> |
| 273 | + <Divider /> |
| 274 | + </div> |
| 275 | + <ActionMenu.Item.Button |
| 276 | + status="danger" |
| 277 | + leadingIcon={IconTrash} |
| 278 | + on:click={() => handleDeleteProject(project)} |
| 279 | + >Delete project</ActionMenu.Item.Button> |
229 | 280 | </ActionMenu.Root> |
230 | 281 | </Popover> |
231 | 282 | </div> |
|
275 | 326 | </svelte:fragment> |
276 | 327 | </Modal> |
277 | 328 |
|
| 329 | +<!-- Delete Confirmation Modal --> |
| 330 | +<Modal |
| 331 | + size="s" |
| 332 | + bind:show={showDeleteModal} |
| 333 | + title="Delete project" |
| 334 | + onSubmit={confirmDelete} |
| 335 | + bind:error={deleteError}> |
| 336 | + <svelte:fragment slot="description"> |
| 337 | + The archived project <strong>{projectToDelete?.name}</strong> will be deleted along with all |
| 338 | + of its metadata, stats, and other resources. |
| 339 | + <b>This action is irreversible.</b> |
| 340 | + </svelte:fragment> |
| 341 | + |
| 342 | + <InputText |
| 343 | + label={`Enter "${projectToDelete?.name}" to continue`} |
| 344 | + placeholder="Enter name" |
| 345 | + id="delete-project-name" |
| 346 | + autofocus |
| 347 | + required |
| 348 | + bind:value={deleteProjectName} /> |
| 349 | + |
| 350 | + <svelte:fragment slot="footer"> |
| 351 | + <Button |
| 352 | + text |
| 353 | + on:click={() => { |
| 354 | + resetDeleteState(); |
| 355 | + }}>Cancel</Button> |
| 356 | + <Button |
| 357 | + submissionLoader |
| 358 | + submit |
| 359 | + disabled={(deleteProjectName ?? '') !== projectToDelete?.name}> |
| 360 | + Delete |
| 361 | + </Button> |
| 362 | + </svelte:fragment> |
| 363 | +</Modal> |
| 364 | + |
278 | 365 | <style> |
279 | 366 | .archive-projects-margin-top { |
280 | 367 | margin-top: 36px; |
281 | 368 | } |
| 369 | + .action-menu-divider { |
| 370 | + margin-inline: -1rem; |
| 371 | + padding-block-start: 0.25rem; |
| 372 | + padding-block-end: 0.25rem; |
| 373 | + } |
282 | 374 |
|
283 | 375 | .archive-projects-margin { |
284 | 376 | margin-top: 16px; |
|
0 commit comments