From f040e8f160d2f7a19900df44dfcc1c7f9a5ab92a Mon Sep 17 00:00:00 2001
From: Regalijan <r@regalijan.com>
Date: Fri, 1 Nov 2024 03:33:10 -0400
Subject: [PATCH] Create short link creation page

---
 app/routes/short-links_.create.tsx | 113 +++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)
 create mode 100644 app/routes/short-links_.create.tsx

diff --git a/app/routes/short-links_.create.tsx b/app/routes/short-links_.create.tsx
new file mode 100644
index 0000000..930c504
--- /dev/null
+++ b/app/routes/short-links_.create.tsx
@@ -0,0 +1,113 @@
+import {
+  Container,
+  FormControl,
+  FormHelperText,
+  FormLabel,
+  Heading,
+  Input,
+  useToast,
+} from "@chakra-ui/react";
+import { useLoaderData } from "@remix-run/react";
+import { useState } from "react";
+
+export function meta() {
+  return [
+    {
+      title: "Create a Short Link",
+    },
+  ];
+}
+
+export async function loader({ context }: { context: RequestContext }) {
+  const userId = context.data.current_user?.id;
+
+  if (!userId)
+    throw new Response(null, {
+      status: 401,
+    });
+
+  if (
+    ![0, 2, 4, 5, 6, 7, 9, 10, 11, 12].find(
+      (i) => context.data.current_user.permissions & (1 << i),
+    )
+  )
+    throw new Response(null, {
+      status: 403,
+    });
+
+  return null;
+}
+
+export default function () {
+  useLoaderData<typeof loader>();
+  const [destination, setDestination] = useState("");
+  const [code, setCode] = useState("");
+  const toast = useToast();
+
+  async function createLink() {
+    // Create random 14 character string if no code is provided
+    const linkCode =
+      code ||
+      Array.from(Array(14), () =>
+        Math.floor(Math.random() * 36).toString(36),
+      ).join("");
+
+    const createResp = await fetch("/api/short-links/new", {
+      body: JSON.stringify({ destination, path: linkCode }),
+      headers: {
+        "content-type": "application/json",
+      },
+      method: "POST",
+    });
+
+    if (!createResp.ok) {
+      let error = "Unknown error";
+
+      try {
+        error = ((await createResp.json()) as { error: string }).error;
+      } catch {}
+
+      toast({
+        description: error,
+        status: "error",
+        title: "Failed to create link",
+      });
+
+      return;
+    }
+
+    toast({
+      description: "You will momentarily be redirected to the short links page",
+      onCloseComplete: () => location.assign("/short-links"),
+      status: "success",
+      title: "Link created",
+    });
+  }
+
+  return (
+    <Container maxW="container.md">
+      <Heading size="xl">Create a Link</Heading>
+      <FormControl isRequired mt="16px">
+        <FormLabel>Destination URL</FormLabel>
+        <Input
+          onChange={(e) => setDestination(e.target.value)}
+          type="url"
+          value={destination}
+        />
+      </FormControl>
+      <FormControl mt="16px">
+        <FormLabel>Link Code</FormLabel>
+        <Input
+          maxLength={256}
+          onChange={(e) => setCode(e.target.value)}
+          placeholder="totally-not-a-rickroll"
+          value={code}
+        />
+        <FormHelperText>
+          Your custom link code. One will be automatically generated if you do
+          not provide one. Max 256 characters.
+        </FormHelperText>
+      </FormControl>
+    </Container>
+  );
+}