car-crushers-portal/app/routes/short-links.tsx

149 lines
3.9 KiB
TypeScript

import {
Container,
Heading,
IconButton,
Link,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
useToast,
} from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
export function meta() {
return [
{
title: "Short Link Manager - Car Crushers",
},
];
}
export async function loader({ context }: { context: RequestContext }) {
const userId = context.data.current_user?.id;
if (!userId)
throw new Response(null, {
status: 401,
});
if (
typeof [0, 2, 4, 5, 6, 7, 9, 10, 11, 12].find(
(i) => context.data.current_user.permissions & (1 << i),
) === "undefined"
)
throw new Response(null, {
status: 403,
});
const { results } = await context.env.D1.prepare(
"SELECT destination, path FROM short_links WHERE user = ?;",
)
.bind(userId)
.all();
return results as Record<string, string>[];
}
export default function () {
const data = useLoaderData<typeof loader>();
const toast = useToast();
async function deleteLink(path: string) {
const deleteResp = await fetch(
`/api/short-links/${encodeURIComponent(path)}`,
{
method: "DELETE",
},
);
if (!deleteResp.ok) {
let error = "Unknown error";
try {
error = ((await deleteResp.json()) as { error: string }).error;
} catch {}
toast({
description: error,
status: "error",
title: "Failed to delete link",
});
return;
}
toast({
description: `Link ${path} was successfully deleted.`,
onCloseComplete: () => location.reload(),
status: "success",
title: "Deleted",
});
}
return (
<Container maxW="container.lg">
<Heading mb={8}>Short Links</Heading>
<TableContainer mx="16px">
<Table variant="simple">
<TableCaption>Your short links</TableCaption>
<Thead>
<Tr>
<Th>Destination</Th>
<Th>Code</Th>
<Th>Delete</Th>
</Tr>
</Thead>
<Tbody>
{data.map((entry) => {
return (
<Tr>
<Td>{entry.destination}</Td>
<Td
onClick={() => {
navigator.clipboard.writeText(
encodeURIComponent(
`https://carcrushe.rs/${entry.path}`,
),
);
alert("Link copied");
}}
>
https://carcrushe.rs/{entry.path}
</Td>
<Td>
<IconButton
aria-label="Delete link"
colorScheme="red"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z" />
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z" />
</svg>
}
onClick={async () => await deleteLink(entry.path)}
/>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<Link href="/short-links/create" mt="8px">
Create a new link
</Link>
</Container>
);
}