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
```
# If not using docker, use 0.0.0.0:8000
DB_PORT=surrealdb:8000
DB_URL_PORT=surrealdb:8000
DB_USER=root
DB_PASSWORD=root
```

43
package-lock.json generated
View file

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

View file

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

View file

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

View file

@ -2,18 +2,14 @@
import { notFound, redirect } from "next/navigation";
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 } }) {
if (params.slug == "favicon.ico") {
return;
}
if (params.slug == "favicon.ico") {
return;
}
let long_url = await querydb(params.slug);
if (long_url == undefined) {
return notFound();
}
redirect(`https://${long_url}`);
let long_url = await querydb(params.slug);
if (long_url == undefined) {
return notFound();
}
redirect(`https://${long_url}`);
}

View file

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

View file

@ -3,24 +3,30 @@ import { initConnection } from "@/components/db-utils";
import { formSchema } from "./schema";
export async function querydb(prevState: any, formData: FormData) {
const values = formSchema.safeParse(formData)
if (!values.success) {
return { error: values.error };
try {
const values = formSchema.safeParse(formData)
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";
export const formSchema = z.object({
url: z.string().min(4,
{ message: "The URL must be at least 4 characters long" }
).max(100
, { message: "The URL must be at most 100 characters long" }),
});
url: z.string().min(4,
{ message: "The URL must be at least 4 characters long" }
).max(100
, { message: "The URL must be at most 100 characters long" }),
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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