NewRepositoryDialog.svelte
1 <script lang="ts"> 2 import { enhance } from "$app/forms"; 3 import { Button } from "$lib/components/ui/button"; 4 import * as Dialog from "$lib/components/ui/dialog"; 5 import { Input } from "$lib/components/ui/input"; 6 import { cn, parseRepositoryId, truncateText } from "$lib/utils"; 7 8 import { toast } from "svelte-sonner"; 9 10 import Icon from "./Icon.svelte"; 11 12 let { nodeId, existingRids }: { nodeId: string; existingRids: string[] } = 13 $props(); 14 15 let rid = $state(""); 16 let open = $state(false); 17 let isSubmitting = $state(false); 18 19 let validationError = $derived.by(() => { 20 if (!rid) return null; 21 const parsed = parseRepositoryId(rid); 22 if (!parsed) return "That's not a valid repository ID"; 23 const normalizedRid = `${parsed.prefix}${parsed.pubkey}`; 24 if (existingRids.includes(normalizedRid)) { 25 return "This repository is already seeded"; 26 } 27 return null; 28 }); 29 </script> 30 31 <Dialog.Root bind:open> 32 <Dialog.Trigger> 33 <Button class="w-max" variant="primary"> 34 <Icon name="seed" /> 35 Add a repo 36 </Button> 37 </Dialog.Trigger> 38 <Dialog.Content> 39 <form 40 method="POST" 41 action="?/seed" 42 class="flex flex-col gap-4" 43 use:enhance={() => { 44 isSubmitting = true; 45 return async ({ result, update }) => { 46 isSubmitting = false; 47 if (result.type === "success") { 48 const seededRid = (result.data as { rid?: string })?.rid; 49 rid = ""; 50 open = false; 51 await update(); 52 if (seededRid) { 53 toast.success( 54 `Repository ${truncateText(seededRid)} added successfully`, 55 ); 56 } 57 } else if (result.type === "failure") { 58 toast.error( 59 (result.data as { error?: string })?.error || 60 "Failed to seed repository", 61 ); 62 } 63 }; 64 }}> 65 <Dialog.Header> 66 <Dialog.Title>Seed repo</Dialog.Title> 67 </Dialog.Header> 68 <Dialog.Description class="flex flex-col gap-4"> 69 <div class="text-muted-foreground text-sm"> 70 Adding a repository to your Garden makes it more available to other 71 users on Radicle. 72 </div> 73 <input type="hidden" name="nodeId" value={nodeId} /> 74 <div class="flex flex-col gap-1"> 75 <Input 76 type="text" 77 name="rid" 78 placeholder="Enter repository ID (rad:…)" 79 autocomplete="off" 80 autocorrect="off" 81 autocapitalize="off" 82 spellcheck="false" 83 bind:value={rid} 84 aria-invalid={!!validationError} 85 class={cn( 86 "font-mono placeholder:font-sans", 87 validationError ? "border-feedback-error-border" : "", 88 )} /> 89 {#if validationError} 90 <div class="text-sm text-feedback-error-text"> 91 {validationError} 92 </div> 93 {/if} 94 </div> 95 </Dialog.Description> 96 <Dialog.Footer class="grid grid-cols-2 gap-2"> 97 <Button 98 type="button" 99 onclick={() => { 100 open = false; 101 rid = ""; 102 }}> 103 Cancel 104 </Button> 105 <Button 106 variant="primary" 107 type="submit" 108 disabled={!rid || isSubmitting || !!validationError}> 109 {isSubmitting ? "Adding…" : "Add"} 110 </Button> 111 </Dialog.Footer> 112 </form> 113 </Dialog.Content> 114 </Dialog.Root>