This commit is contained in:
Elijah McMorris 2024-02-17 13:49:31 -08:00
parent c552d0dc8f
commit f9afa2501d
Signed by: NexVeridian
SSH key fingerprint: SHA256:bsA1SKZxuEcEVHAy3gY1HUeM5ykRJl0U0kQHQn0hMg8
20 changed files with 212 additions and 215 deletions

View file

@ -1,7 +1,7 @@
## Example .env ## Example .env
``` ```
# If not using docker, use 0.0.0.0:8000 # If not using docker, use 0.0.0.0:8000
DB_PORT=surrealdb:8000 DB_URL_PORT=surrealdb:8000
DB_USER=root DB_USER=root
DB_PASSWORD=root DB_PASSWORD=root
``` ```

43
package-lock.json generated
View file

@ -26,7 +26,7 @@
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.50.1", "react-hook-form": "^7.50.1",
"surrealdb.js": "^0.11.0", "surrealdb.js": "^0.11.0",
"swr": "^2.2.4", "swr": "^2.2.5",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"ws": "^8.16.0", "ws": "^8.16.0",
@ -35,7 +35,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/prop-types": "^15.7.11", "@types/prop-types": "^15.7.11",
"@types/react": "^18.2.55", "@types/react": "^18.2.56",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"eslint": "^8", "eslint": "^8",
@ -44,7 +44,7 @@
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11", "prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.3.3" "typescript": "^5"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -1274,9 +1274,9 @@
"devOptional": true "devOptional": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.2.55", "version": "18.2.56",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.56.tgz",
"integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==", "integrity": "sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==",
"devOptional": true, "devOptional": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@ -1888,9 +1888,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001587", "version": "1.0.30001588",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz",
"integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -2176,9 +2176,9 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.671", "version": "1.4.673",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.671.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz",
"integrity": "sha512-UUlE+/rWbydmp+FW8xlnnTA5WNA0ZZd2XL8CuMS72rh+k4y1f8+z6yk3UQhEwqHQWj6IBdL78DwWOdGMvYfQyA==", "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==",
"dev": true "dev": true
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
@ -5291,9 +5291,9 @@
} }
}, },
"node_modules/swr": { "node_modules/swr": {
"version": "2.2.4", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz", "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
"integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==", "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
"dependencies": { "dependencies": {
"client-only": "^0.0.1", "client-only": "^0.0.1",
"use-sync-external-store": "^1.2.0" "use-sync-external-store": "^1.2.0"
@ -5494,16 +5494,17 @@
} }
}, },
"node_modules/typed-array-byte-offset": { "node_modules/typed-array-byte-offset": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.1.tgz",
"integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", "integrity": "sha512-tcqKMrTRXjqvHN9S3553NPCaGL0VPgFI92lXszmrE8DMhiDPLBYLlvo8Uu4WZAAX/aGqp/T1sbA4ph8EWjDF9Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"available-typed-arrays": "^1.0.5", "available-typed-arrays": "^1.0.6",
"call-bind": "^1.0.2", "call-bind": "^1.0.7",
"for-each": "^0.3.3", "for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-proto": "^1.0.1", "has-proto": "^1.0.1",
"is-typed-array": "^1.1.10" "is-typed-array": "^1.1.13"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"

View file

@ -27,7 +27,7 @@
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.50.1", "react-hook-form": "^7.50.1",
"surrealdb.js": "^0.11.0", "surrealdb.js": "^0.11.0",
"swr": "^2.2.4", "swr": "^2.2.5",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"ws": "^8.16.0", "ws": "^8.16.0",
@ -36,7 +36,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/prop-types": "^15.7.11", "@types/prop-types": "^15.7.11",
"@types/react": "^18.2.55", "@types/react": "^18.2.56",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.17",
"eslint": "^8", "eslint": "^8",

View file

@ -2,17 +2,17 @@
import { initConnection } from "@/components/db-utils"; import { initConnection } from "@/components/db-utils";
export async function querydb(slug: string) { export async function querydb(slug: string) {
try { try {
let db = await initConnection(); let db = await initConnection();
let long_url = await db.query(` let long_url = await db.query(`
update url:[$id] set clicks = clicks + 1 ; update url:[$id] set clicks = clicks + 1 ;
select * from url:[$id]; select * from url:[$id];
`, { id: slug }); `, { id: slug });
// @ts-ignore // @ts-ignore
long_url = long_url[0][0].long_url; long_url = long_url[0][0].long_url;
return long_url; return long_url;
} catch (e) { } catch (e) {
return; return;
} }
} }

View file

@ -2,18 +2,14 @@
import { notFound, redirect } from "next/navigation"; import { notFound, redirect } from "next/navigation";
import { querydb } from "./db"; import { querydb } from "./db";
// export default function Page({ params }: { params: { slug: string } }) {
// redirect(`https://${params.slug}`)
// }
export default async function Page({ params }: { params: { slug: string } }) { export default async function Page({ params }: { params: { slug: string } }) {
if (params.slug == "favicon.ico") { if (params.slug == "favicon.ico") {
return; return;
} }
let long_url = await querydb(params.slug); let long_url = await querydb(params.slug);
if (long_url == undefined) { if (long_url == undefined) {
return notFound(); return notFound();
} }
redirect(`https://${long_url}`); redirect(`https://${long_url}`);
} }

View file

@ -23,7 +23,7 @@ import {
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CopyIcon } from "@radix-ui/react-icons"; import { CopyIcon } from "@radix-ui/react-icons";
import { useFormState, useFormStatus } from "react-dom"; import { useFormState } from "react-dom";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { querydb } from "./db"; import { querydb } from "./db";
@ -33,23 +33,16 @@ const initialState = {
url: null, url: null,
} }
function SubmitButton() {
const { pending } = useFormStatus();
return (
<Button type="submit">Submit</Button>
);
}
export default function CreateCard() { export default function CreateCard() {
// @ts-ignore // @ts-ignore
const [state, formAction] = useFormState(querydb, initialState); let [state, formAction] = useFormState(querydb, initialState);
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
url: "", url: "",
}, },
}) });
const handleCopyUrl = () => { const handleCopyUrl = () => {
if (state && state.url) { if (state && state.url) {
@ -80,7 +73,7 @@ export default function CreateCard() {
<CardContent> <CardContent>
<Form {...form}> <Form {...form}>
{/* @ts-ignore */} {/* @ts-ignore */}
<form action={form.handleSubmit(formAction)} className="space-y-8"> <form id="url_form" action={form.handleSubmit(formAction)} className="space-y-8">
<FormField <FormField
control={form.control} control={form.control}
name="url" name="url"
@ -94,12 +87,12 @@ export default function CreateCard() {
</FormItem> </FormItem>
)} )}
/> />
<SubmitButton />
</form> </form>
</Form> </Form>
</CardContent> </CardContent>
<CardFooter> <CardFooter className="flex flex-row gap-4">
<Button type="submit" form="url_form">Submit</Button>
{state && state.url && ( {state && state.url && (
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>

View file

@ -3,24 +3,30 @@ import { initConnection } from "@/components/db-utils";
import { formSchema } from "./schema"; import { formSchema } from "./schema";
export async function querydb(prevState: any, formData: FormData) { export async function querydb(prevState: any, formData: FormData) {
const values = formSchema.safeParse(formData) try {
if (!values.success) { const values = formSchema.safeParse(formData)
return { error: values.error }; if (!values.success) {
return { error: values.error };
}
const long_url = values.data.url;
let db = await initConnection();
console.log(db);
let url = await db.query(`
create url:[rand::string(8)] CONTENT {
long_url: string::replace(string::replace($long_url, "https://", ""), "http://", ""),
clicks: 0,
date_added: time::now(),
date_accessed: <future> { time::now() }
} return id[0];
`, { long_url: long_url });
// @ts-ignore
url = url[0][0].id;
console.log("URL", url);
return { url: url };
} catch (e) {
return;
} }
const long_url = values.data.url;
let db = await initConnection();
let url = await db.query(`
create url:[rand::string(8)] CONTENT {
long_url: string::replace(string::replace($long_url, "https://", ""), "http://", ""),
clicks: 0,
date_added: time::now(),
date_accessed: <future> { time::now() }
} return id[0];
`, { long_url: long_url });
// @ts-ignore
url = url[0][0].id;
return { url: url };
} }

View file

@ -1,8 +1,9 @@
"use server";
import { z } from "zod"; import { z } from "zod";
export const formSchema = z.object({ export const formSchema = z.object({
url: z.string().min(4, url: z.string().min(4,
{ message: "The URL must be at least 4 characters long" } { message: "The URL must be at least 4 characters long" }
).max(100 ).max(100
, { message: "The URL must be at most 100 characters long" }), , { message: "The URL must be at most 100 characters long" }),
}); });

View file

@ -1,23 +1,23 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import {
Card, Card,
CardHeader, CardHeader,
CardTitle CardTitle
} from "@/components/ui/card"; } from "@/components/ui/card";
export default function GlobalError({ export default function GlobalError({
error, error,
}: { }: {
error: Error & { digest?: string } error: Error & { digest?: string }
}) { }) {
return ( return (
<CardGrid max_rows={1}> <CardGrid max_rows={1}>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-center text-2xl text-red-400">Error</CardTitle> <CardTitle className="text-center text-2xl text-red-400">Error</CardTitle>
</CardHeader> </CardHeader>
</Card> </Card>
</CardGrid> </CardGrid>
); );
} }

View file

@ -1,4 +1,4 @@
import Nav from "@/components/Nav"; import Nav from "@/components/nav";
import { ThemeProvider } from "@/components/theme-provider"; import { ThemeProvider } from "@/components/theme-provider";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Roboto_Mono } from "next/font/google"; import { Roboto_Mono } from "next/font/google";

View file

@ -1,23 +1,23 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import {
Card, Card,
CardHeader, CardHeader,
CardTitle CardTitle
} from "@/components/ui/card"; } from "@/components/ui/card";
export default function GlobalError({ export default function GlobalError({
error, error,
}: { }: {
error: Error & { digest?: string } error: Error & { digest?: string }
}) { }) {
return ( return (
<CardGrid max_rows={1}> <CardGrid max_rows={1}>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-center text-2xl text-red-400">404 - Not Found</CardTitle> <CardTitle className="text-center text-2xl text-red-400">404 - Not Found</CardTitle>
</CardHeader> </CardHeader>
</Card> </Card>
</CardGrid> </CardGrid>
); );
} }

View file

@ -9,7 +9,6 @@ export async function querydb() {
order by clicks desc order by clicks desc
limit 50; limit 50;
`); `);
return stats; return stats;
} catch (e) { } catch (e) {
return; return;

View file

@ -1,23 +1,23 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import {
Card, Card,
CardHeader, CardHeader,
CardTitle CardTitle
} from "@/components/ui/card"; } from "@/components/ui/card";
export default function GlobalError({ export default function GlobalError({
error, error,
}: { }: {
error: Error & { digest?: string } error: Error & { digest?: string }
}) { }) {
return ( return (
<CardGrid max_rows={1}> <CardGrid max_rows={1}>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-1xl text-amber-400">Loading...</CardTitle> <CardTitle className="text-1xl text-amber-400">Loading...</CardTitle>
</CardHeader> </CardHeader>
</Card> </Card>
</CardGrid> </CardGrid>
); );
} }

View file

@ -1,3 +1,4 @@
"use server";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { columns } from "./columns"; import { columns } from "./columns";
@ -5,37 +6,37 @@ import { DataTable } from "./data-table";
import { querydb } from "./db"; import { querydb } from "./db";
export default async function StatsPage() { export default async function StatsPage() {
let data = await querydb(); let data = await querydb();
// @ts-ignore // @ts-ignore
data = data[0]; data = data[0];
if (data !== undefined) { if (data !== undefined) {
const formatDate = (dateString: string | number | Date) => { const formatDate = (dateString: string | number | Date) => {
const date = new Date(dateString); const date = new Date(dateString);
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const year = String(date.getFullYear()).slice(-2); const year = String(date.getFullYear()).slice(-2);
return `${month}/${day}/${year}`; return `${month}/${day}/${year}`;
}; };
data = data.map(item => ({ data = data.map(item => ({
// @ts-ignore // @ts-ignore
...item, ...item,
// @ts-ignore // @ts-ignore
date_accessed: formatDate(item.date_accessed), date_accessed: formatDate(item.date_accessed),
// @ts-ignore // @ts-ignore
date_added: formatDate(item.date_added), date_added: formatDate(item.date_added),
// @ts-ignore // @ts-ignore
id: item.id.replace(/^url:\['(.*)'\]$/, '$1') id: item.id.replace(/^url:\['(.*)'\]$/, '$1')
})); }));
} }
return ( return (
<CardGrid max_rows={1}> <CardGrid max_rows={1}>
<Card> <Card>
{/* @ts-ignore */} {/* @ts-ignore */}
<DataTable columns={columns} data={data} /> <DataTable columns={columns} data={data} />
</Card> </Card>
</CardGrid> </CardGrid>
); );
} }

View file

@ -1,7 +1,8 @@
"use server";
import DarkModeToggle from "@/components/dark-mode-toggle"; import DarkModeToggle from "@/components/dark-mode-toggle";
import Link from "next/link"; import Link from "next/link";
export default function Nav() { export default async function Nav() {
return ( return (
<nav className="relative flex flex-row place-items-center gap-4 p-2 px-4 font-medium border-b"> <nav className="relative flex flex-row place-items-center gap-4 p-2 px-4 font-medium border-b">
<div className="text-2xl"> <div className="text-2xl">

View file

@ -1,43 +1,44 @@
"use client";
export default function CardGrid({ export default function CardGrid({
max_rows = 4, max_rows = 4,
children, children,
className, className,
...props ...props
}: { }: {
max_rows?: number; max_rows?: number;
children?: React.ReactNode; children?: React.ReactNode;
className?: string; className?: string;
}) { }) {
let baseClassName = `hidden items-start justify-center gap-6 rounded-lg p-8 md:grid`; let baseClassName = `hidden items-start justify-center gap-6 rounded-lg p-8 md:grid`;
switch (max_rows) { switch (max_rows) {
case 1: case 1:
baseClassName += " md:grid-cols-1 "; baseClassName += " md:grid-cols-1 ";
break; break;
case 2: case 2:
baseClassName += " md:grid-cols-1 lg:grid-cols-2 "; baseClassName += " md:grid-cols-1 lg:grid-cols-2 ";
break; break;
case 3: case 3:
baseClassName += " md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 "; baseClassName += " md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 ";
break; break;
case 4: case 4:
baseClassName += " md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 "; baseClassName += " md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 ";
break; break;
default: default:
break; break;
}; };
if (className == undefined) { if (className == undefined) {
className = baseClassName; className = baseClassName;
} else { } else {
className = baseClassName + className; className = baseClassName + className;
} }
return ( return (
<div <div
className={`hidden items-start justify-center gap-6 rounded-lg p-8 md:grid " ${className}`} className={`hidden items-start justify-center gap-6 rounded-lg p-8 md:grid " ${className}`}
{...props} {...props}
> >
{children} {children}
</div> </div>
); );
} }

View file

@ -1,4 +1,4 @@
"use client";; "use client";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";

View file

@ -1,23 +1,21 @@
import { Surreal } from 'surrealdb.js'; "use server";
require('dotenv'); import "dotenv";
import { Surreal } from "surrealdb.js";
const db = new Surreal(); const db = new Surreal();
export async function initConnection(): Promise<Surreal> { export async function initConnection(): Promise<Surreal> {
try { try {
db.connect("http://" + process.env.DB_PORT + "/rpc", { db.connect("http://" + process.env.DB_URL_PORT + "/rpc", {
namespace: 'url', namespace: "url",
database: 'url', database: "url",
auth: {
username: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "root",
},
});
} catch (e) {
console.error("ERROR", e);
}
// @ts-ignore return db;
auth: {
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
},
});
} catch (e) {
console.error('ERROR', e);
}
return db;
} }

View file

@ -1,4 +1,4 @@
"use client";; "use client";
import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types"; import { type ThemeProviderProps } from "next-themes/dist/types";

View file

@ -32,7 +32,7 @@
"next-env.d.ts", "next-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts" ".next/types/**/*.ts",
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"