/ src / components / NewRepositoryDialog.svelte
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>