chore: format

This commit is contained in:
Elijah McMorris 2025-03-06 01:31:59 -08:00
parent c8338e5516
commit 527139f298
Signed by: NexVeridian
SSH key fingerprint: SHA256:bsA1SKZxuEcEVHAy3gY1HUeM5ykRJl0U0kQHQn0hMg8
33 changed files with 848 additions and 3132 deletions

View file

@ -1,16 +0,0 @@
# https://mcr.microsoft.com/en-us/product/devcontainers/typescript-node/about
FROM mcr.microsoft.com/devcontainers/typescript-node:20-bookworm
RUN apt-get update && \
apt-get install build-essential xz-utils
# RUN LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | grep -Po '"tag_name": "v\K[0-9.]+') && \
# curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz" && \
# sudo tar xf lazygit.tar.gz -C /usr/local/bin lazygit && \
# rm -rf lazygit.tar.gz
# RUN BTOP_VERSION=$(curl -s "https://api.github.com/repos/aristocratos/btop/releases/latest" | grep -Po '"tag_name": "v\K[0-9.]+') && \
# wget "https://github.com/aristocratos/btop/releases/download/v${BTOP_VERSION}/btop-x86_64-linux-musl.tbz" && \
# sudo tar -xvf btop-x86_64-linux-musl.tbz && \
# cd btop && ./install.sh && cd .. && \
# rm -rf btop-x86_64-linux-musl.tbz btop

View file

@ -1,60 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
// "image": "mcr.microsoft.com/devcontainers/typescript-node:0-20",
"build": {
// Path is relataive to the devcontainer.json file.
"dockerfile": "Dockerfile"
},
// https://github.com/microsoft/vscode-remote-release/issues/2485#issuecomment-1156342780
"runArgs": [
"--name",
"devcontainer-${containerWorkspaceFolderBasename}"
],
"initializeCommand": "docker rm -f devcontainer-${containerWorkspaceFolderBasename} || true",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/nix:1": {
"packages": [
"btop",
"lazygit",
"nixpkgs-fmt"
],
"extraNixConfig": "experimental-features = nix-command flakes"
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
"postAttachCommand": {
"AddGitSafeDir": "git config --global --add safe.directory /workspaces/${containerWorkspaceFolderBasename}"
},
"onCreateCommand": {
"nix-shell": "nix-shell --command 'echo done install packages'"
},
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "root",
"customizations": {
"vscode": {
"extensions": [
"mutantdino.resourcemonitor",
"christian-kohler.path-intellisense",
"Gruntfuggly.todo-tree",
"ms-azuretools.vscode-docker",
"redhat.vscode-yaml",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"esbenp.prettier-vscode",
"GitHub.copilot",
"GitHub.copilot-chat"
]
}
}
}

View file

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

36
biome.json Normal file
View file

@ -0,0 +1,36 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": [".next", "./src/components/ui"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": { "noForEach": "off", "noBannedTypes": "off" },
"suspicious": {
"noShadowRestrictedNames": "off",
"noExplicitAny": "off",
"noArrayIndexKey": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

View file

@ -1,16 +1,16 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "style": "new-york",
"rsc": false, "rsc": false,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.ts",
"css": "src/app/globals.css", "css": "src/app/globals.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true "cssVariables": true
}, },
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils" "utils": "@/lib/utils"
} }
} }

View file

@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: "standalone", output: "standalone",
} };
module.exports = nextConfig module.exports = nextConfig;

View file

@ -1,50 +1,48 @@
{ {
"name": "next-url-shortener", "name": "next-url-shortener",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbo", "dev": "next dev --turbo",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "fix": "biome check . --write",
}, "check": "biome check ."
"dependencies": { },
"@hookform/resolvers": "^3.10.0", "dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.6", "@hookform/resolvers": "^3.10.0",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-select": "^2.1.6", "@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-select": "^2.1.6",
"@tanstack/react-table": "^8.21.2", "@radix-ui/react-slot": "^1.1.2",
"class-variance-authority": "^0.7.1", "@tanstack/react-table": "^8.21.2",
"clsx": "^2.1.1", "class-variance-authority": "^0.7.1",
"dotenv": "^16.4.7", "clsx": "^2.1.1",
"next": "14.2.5", "dotenv": "^16.4.7",
"next-themes": "^0.3.0", "next": "14.2.5",
"postgres": "^3.4.5", "next-themes": "^0.3.0",
"react": "^18.3.1", "postgres": "^3.4.5",
"react-hook-form": "^7.54.2", "react": "^18.3.1",
"surrealdb.js": "^1.0.0", "react-hook-form": "^7.54.2",
"swr": "^2.3.2", "surrealdb.js": "^1.0.0",
"tailwind-merge": "^2.6.0", "swr": "^2.3.2",
"tailwindcss-animate": "^1.0.7", "tailwind-merge": "^2.6.0",
"ws": "^8.18.1", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2" "ws": "^8.18.1",
}, "zod": "^3.24.2"
"devDependencies": { },
"@types/node": "^22.13.9", "devDependencies": {
"@types/prop-types": "^15.7.14", "@biomejs/biome": "1.9.4",
"@types/react": "^18.3.18", "@types/node": "^22.13.9",
"@types/react-dom": "^18.3.5", "@types/prop-types": "^15.7.14",
"autoprefixer": "^10.4.20", "@types/react": "^18.3.18",
"eslint": "^8.57.1", "@types/react-dom": "^18.3.5",
"eslint-config-next": "14.2.5", "autoprefixer": "^10.4.20",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"prettier": "^3.5.3", "tailwindcss": "^3.4.17",
"prettier-plugin-tailwindcss": "^0.6.11", "typescript": "^5.8.2"
"tailwindcss": "^3.4.17", }
"typescript": "^5.8.2"
}
} }

2401
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View file

@ -1,9 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
git
btop
lazygit
micro
];
}

View file

@ -1,34 +1,40 @@
"use server"; "use server";
import { initConnectionPostgres, initConnectionSurreal } from "@/components/db-utils"; import {
initConnectionPostgres,
initConnectionSurreal,
} from "@/components/db-utils";
export async function querydb(slug: string) { export async function querydb(slug: string) {
let long_url = undefined; let long_url = undefined;
try { try {
if (process.env.DB_TYPE === "surrealdb") { if (process.env.DB_TYPE === "surrealdb") {
let db = await initConnectionSurreal(); const db = await initConnectionSurreal();
long_url = await db.query(` long_url = await db.query(
`
update url:[$id] update url:[$id]
set clicks +=1; set clicks +=1;
`, { id: slug }); `,
// @ts-ignore { id: slug },
long_url = long_url[0][0].long_url; );
} // @ts-ignore
long_url = long_url[0][0].long_url;
}
if (process.env.DB_TYPE === "postgres") { if (process.env.DB_TYPE === "postgres") {
let sql = await initConnectionPostgres(); const sql = await initConnectionPostgres();
long_url = await sql` long_url = await sql`
update url set update url set
clicks = clicks + 1, clicks = clicks + 1,
date_accessed = now() date_accessed = now()
where id = ${slug} where id = ${slug}
returning long_url; returning long_url;
`; `;
long_url = long_url[0].long_url; long_url = long_url[0].long_url;
} }
console.log(long_url); console.log(long_url);
return long_url; return long_url;
} catch (e) { } catch (e) {
return; return;
} }
} }

View file

@ -3,13 +3,13 @@ import { notFound, redirect } from "next/navigation";
import { querydb } from "./db"; import { querydb } from "./db";
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); const 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

@ -1,117 +1,116 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
CardContent, CardContent,
CardFooter, CardFooter,
CardHeader, CardHeader,
CardTitle CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { import {
Form, Form,
FormControl, FormControl,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} 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 } 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 type { z } from "zod";
import { querydb } from "./db"; import { querydb } from "./db";
import { formSchema } from "./schema"; import { formSchema } from "./schema";
const initialState = { const initialState = {
url: null, url: null,
} };
export default function CreateCard() { export default function CreateCard() {
// @ts-ignore // @ts-ignore
let [state, formAction] = useFormState(querydb, initialState); const [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) {
let url = undefined; let url = undefined;
const currentSiteName = window.location.hostname; const currentSiteName = window.location.hostname;
if (state.url.includes("https://")) { if (state.url.includes("https://")) {
url = state.url; url = state.url;
} else { } else {
if (currentSiteName === "localhost" || currentSiteName === "0.0.0.0") { if (currentSiteName === "localhost" || currentSiteName === "0.0.0.0") {
url = `http://${currentSiteName}:${window.location.port}/${state.url.toString()}`; url = `http://${currentSiteName}:${window.location.port}/${state.url.toString()}`;
} else { } else {
url = `https://${currentSiteName}/${state.url.toString()}`; url = `https://${currentSiteName}/${state.url.toString()}`;
} }
} }
navigator.clipboard.writeText(url) navigator.clipboard.writeText(url).catch((err) => {
.catch(err => { console.error("Failed to copy URL to clipboard:", err);
console.error('Failed to copy URL to clipboard:', err); });
}); }
} };
};
return ( return (
// <CardGrid> // <CardGrid>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Create a Shortened URL</CardTitle> <CardTitle>Create a Shortened URL</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Form {...form}> <Form {...form}>
{/* @ts-ignore */} {/* @ts-ignore */}
<form id="url_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"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Enter a url</FormLabel> <FormLabel>Enter a url</FormLabel>
<FormControl> <FormControl>
<Input placeholder="nexveridian.com" {...field} /> <Input placeholder="nexveridian.com" {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</form> </form>
</Form> </Form>
</CardContent> </CardContent>
<CardFooter className="flex flex-row gap-4"> <CardFooter className="flex flex-row gap-4">
<Button type="submit" form="url_form">Submit</Button> <Button type="submit" form="url_form">
{state && state.url && ( Submit
<Popover> </Button>
<PopoverTrigger> {state && state.url && (
<Button onClick={handleCopyUrl}> <Popover>
<CopyIcon className="mr-2 h-4 w-4" /> Copy Url <PopoverTrigger>
</Button> <Button onClick={handleCopyUrl}>
</PopoverTrigger> <CopyIcon className="mr-2 h-4 w-4" /> Copy Url
<PopoverContent> </Button>
<p className="text-lg"> </PopoverTrigger>
Url added to clipboard <PopoverContent>
</p> <p className="text-lg">Url added to clipboard</p>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
)} )}
</CardFooter> </CardFooter>
</Card> </Card>
// </CardGrid> // </CardGrid>
); );
} }

View file

@ -1,47 +1,54 @@
"use server"; "use server";
import { initConnectionPostgres, initConnectionSurreal } from "@/components/db-utils"; import {
initConnectionPostgres,
initConnectionSurreal,
} from "@/components/db-utils";
import { formSchema } from "./schema"; import { formSchema } from "./schema";
function generateRandomString(length: number) { function generateRandomString(length: number) {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charset =
let randomString = ''; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < length; i++) { let randomString = "";
const randomIndex = Math.floor(Math.random() * charset.length); for (let i = 0; i < length; i++) {
randomString += charset[randomIndex]; const randomIndex = Math.floor(Math.random() * charset.length);
} randomString += charset[randomIndex];
return randomString; }
return randomString;
} }
export async function querydb(prevState: any, formData: FormData) { export async function querydb(prevState: any, formData: FormData) {
const values = formSchema.safeParse(formData) const values = formSchema.safeParse(formData);
if (!values.success) { if (!values.success) {
return { error: values.error }; return { error: values.error };
} }
let long_url = values.data.url.replace("https://", "").replace("http://", ""); let long_url = values.data.url.replace("https://", "").replace("http://", "");
long_url = long_url.endsWith('/') ? long_url.slice(0, -1) : long_url; long_url = long_url.endsWith("/") ? long_url.slice(0, -1) : long_url;
let url = undefined; let url = undefined;
try { try {
if (process.env.DB_TYPE === "surrealdb") { if (process.env.DB_TYPE === "surrealdb") {
let db = await initConnectionSurreal(); const db = await initConnectionSurreal();
url = await db.query(` url = await db.query(
`
create url:[rand::string(8)] CONTENT { create url:[rand::string(8)] CONTENT {
long_url: $long_url, long_url: $long_url,
clicks: 0, clicks: 0,
date_added: time::now(), date_added: time::now(),
date_accessed: <future> { time::now() } date_accessed: <future> { time::now() }
} return id[0]; } return id[0];
`, { `,
long_url: long_url {
}); long_url: long_url,
// @ts-ignore },
url = url[0][0].id; );
} // @ts-ignore
url = url[0][0].id;
}
console.log(long_url); console.log(long_url);
if (process.env.DB_TYPE === "postgres") { if (process.env.DB_TYPE === "postgres") {
let sql = await initConnectionPostgres(); const sql = await initConnectionPostgres();
await sql` await sql`
create table if not exists url ( create table if not exists url (
id text primary key, id text primary key,
clicks integer not null, clicks integer not null,
@ -51,7 +58,7 @@ export async function querydb(prevState: any, formData: FormData) {
); );
`; `;
url = await sql` url = await sql`
insert into url (id, long_url, clicks, date_added, date_accessed) insert into url (id, long_url, clicks, date_added, date_accessed)
values ( values (
${generateRandomString(8)}, ${generateRandomString(8)},
@ -63,17 +70,17 @@ export async function querydb(prevState: any, formData: FormData) {
returning id; returning id;
`; `;
url = url[0].id; url = url[0].id;
} }
console.log(url); console.log(url);
if (process.env.OVERRIDE_URL !== undefined) { if (process.env.OVERRIDE_URL !== undefined) {
url = `https://${process.env.OVERRIDE_URL}/${url.toString()}`; url = `https://${process.env.OVERRIDE_URL}/${url.toString()}`;
} }
return { url: url }; return { url: url };
} catch (e) { } catch (e) {
return; return;
} }
} }

View file

@ -1,8 +1,8 @@
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
{ message: "The URL must be at least 4 characters long" } .string()
).max(100 .min(4, { message: "The URL must be at least 4 characters long" })
, { message: "The URL must be at most 100 characters long" }), .max(100, { message: "The URL must be at most 100 characters long" }),
}); });

View file

@ -1,23 +1,21 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import { Card, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardHeader,
CardTitle
} 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 maxCols={1}> <CardGrid maxCols={1}>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-center text-2xl text-red-400">Error + {String(error)}</CardTitle> <CardTitle className="text-center text-2xl text-red-400">
</CardHeader> Error + {String(error)}
</Card> </CardTitle>
</CardGrid> </CardHeader>
); </Card>
</CardGrid>
);
} }

View file

@ -3,57 +3,57 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 0 0% 3.9%; --foreground: 0 0% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 0 0% 3.9%; --card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%; --popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%; --primary: 0 0% 9%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%; --secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%; --muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%; --muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%; --accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%; --accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%; --border: 0 0% 89.8%;
--input: 0 0% 89.8%; --input: 0 0% 89.8%;
--ring: 0 0% 3.9%; --ring: 0 0% 3.9%;
--radius: 0.3rem; --radius: 0.3rem;
} }
.dark { .dark {
--background: 0 0% 3.9%; --background: 0 0% 3.9%;
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 0 0% 3.9%; --card: 0 0% 3.9%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%; --popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 0 0% 98%; --primary: 0 0% 98%;
--primary-foreground: 0 0% 9%; --primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%; --secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%; --muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%; --muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%; --accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%; --accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%; --border: 0 0% 14.9%;
--input: 0 0% 14.9%; --input: 0 0% 14.9%;
--ring: 0 0% 83.1%; --ring: 0 0% 83.1%;
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }

View file

@ -5,33 +5,33 @@ import { Roboto_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
export const roboto_mono = Roboto_Mono({ export const roboto_mono = Roboto_Mono({
subsets: ["latin"], subsets: ["latin"],
display: "swap", display: "swap",
variable: "--font-roboto-mono", variable: "--font-roboto-mono",
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Next Url Shortener", title: "Next Url Shortener",
description: "Next Url Shortener", description: "Next Url Shortener",
}; };
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="en"> <html lang="en">
<body className={roboto_mono.className}> <body className={roboto_mono.className}>
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="dark" defaultTheme="dark"
disableTransitionOnChange disableTransitionOnChange
> >
<Nav /> <Nav />
{children} {children}
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </html>
); );
} }

View file

@ -1,19 +1,17 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import { Card, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardHeader,
CardTitle
} from "@/components/ui/card";
export default function NotFound() { export default function NotFound() {
return ( return (
<CardGrid maxCols={1}> <CardGrid maxCols={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">
</CardHeader> 404 - Not Found
</Card> </CardTitle>
</CardGrid> </CardHeader>
); </Card>
</CardGrid>
);
} }

View file

@ -1,32 +1,28 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import { Card, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardHeader,
CardTitle
} from "@/components/ui/card";
import Link from "next/link"; import Link from "next/link";
import CreateCard from "./create/create"; import CreateCard from "./create/create";
export default function Home() { export default function Home() {
return ( return (
<CardGrid> <CardGrid>
{/* <Card> {/* <Card>
<Link href="/create"> <Link href="/create">
<CardHeader> <CardHeader>
<CardTitle>Create a Shortened URL</CardTitle> <CardTitle>Create a Shortened URL</CardTitle>
</CardHeader> </CardHeader>
</Link> </Link>
</Card> */} </Card> */}
<CreateCard /> <CreateCard />
<Card> <Card>
<Link href="/stats"> <Link href="/stats">
<CardHeader> <CardHeader>
<CardTitle>Site Stats</CardTitle> <CardTitle>Site Stats</CardTitle>
</CardHeader> </CardHeader>
</Link> </Link>
</Card> </Card>
</CardGrid> </CardGrid>
); );
} }

View file

@ -1,33 +1,33 @@
"use client"; "use client";
import { ColumnDef } from "@tanstack/react-table"; import type { ColumnDef } from "@tanstack/react-table";
export type UrlTable = { export type UrlTable = {
clicks: number; clicks: number;
date_accessed: Date; date_accessed: Date;
date_added: Date; date_added: Date;
id: string; id: string;
long_url: string; long_url: string;
}; };
export const columns: ColumnDef<UrlTable>[] = [ export const columns: ColumnDef<UrlTable>[] = [
{ {
accessorKey: "clicks", accessorKey: "clicks",
header: "Clicks", header: "Clicks",
}, },
{ {
accessorKey: "long_url", accessorKey: "long_url",
header: "URL", header: "URL",
}, },
{ {
accessorKey: "id", accessorKey: "id",
header: "Short URL", header: "Short URL",
}, },
{ {
accessorKey: "date_accessed", accessorKey: "date_accessed",
header: "Date Accessed", header: "Date Accessed",
}, },
{ {
accessorKey: "date_added", accessorKey: "date_added",
header: "Date Added", header: "Date Added",
}, },
]; ];

View file

@ -1,79 +1,79 @@
"use client"; "use client";
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { import {
ColumnDef, type ColumnDef,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
useReactTable useReactTable,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]; columns: ColumnDef<TData, TValue>[];
data: TData[]; data: TData[];
slug: string[]; slug: string[];
} }
export function DataTable<TData, TValue>({ export function DataTable<TData, TValue>({
columns, columns,
data, data,
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const table = useReactTable({ const table = useReactTable({
data, data,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
return ( return (
<> <>
<Table> <Table>
<TableHeader> <TableHeader>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => { {headerGroup.headers.map((header) => {
return ( return (
<TableHead key={header.id}> <TableHead key={header.id}>
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(
header.column.columnDef.header, header.column.columnDef.header,
header.getContext() header.getContext(),
)} )}
</TableHead> </TableHead>
) );
})} })}
</TableRow> </TableRow>
))} ))}
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{table.getRowModel().rows?.length ? ( {table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => ( table.getRowModel().rows.map((row) => (
<TableRow <TableRow
key={row.id} key={row.id}
data-state={row.getIsSelected() && "selected"} data-state={row.getIsSelected() && "selected"}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}> <TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell> </TableCell>
))} ))}
</TableRow> </TableRow>
)) ))
) : ( ) : (
<TableRow> <TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center"> <TableCell colSpan={columns.length} className="h-24 text-center">
No results. No results.
</TableCell> </TableCell>
</TableRow> </TableRow>
)} )}
</TableBody> </TableBody>
</Table> </Table>
</> </>
); );
} }

View file

@ -1,33 +1,36 @@
"use server"; "use server";
import { initConnectionPostgres, initConnectionSurreal } from "@/components/db-utils"; import {
initConnectionPostgres,
initConnectionSurreal,
} from "@/components/db-utils";
export async function querydb() { export async function querydb() {
try { try {
let stats = []; let stats = [];
if (process.env.DB_TYPE === "surrealdb") { if (process.env.DB_TYPE === "surrealdb") {
let db = await initConnectionSurreal(); const db = await initConnectionSurreal();
// console.log(db); // console.log(db);
stats = await db.query(` stats = await db.query(`
select * from url select * from url
order by clicks desc order by clicks desc
limit 10; limit 10;
`); `);
// @ts-ignore // @ts-ignore
stats = stats[0]; stats = stats[0];
} }
if (process.env.DB_TYPE === "postgres") { if (process.env.DB_TYPE === "postgres") {
let sql = await initConnectionPostgres(); const sql = await initConnectionPostgres();
stats = await sql` stats = await sql`
select * from url select * from url
order by clicks desc order by clicks desc
limit 10; limit 10;
`; `;
} }
return stats; return stats;
} catch (e) { } catch (e) {
return []; return [];
} }
} }

View file

@ -1,19 +1,15 @@
"use client"; "use client";
import CardGrid from "@/components/card-grid"; import CardGrid from "@/components/card-grid";
import { import { Card, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardHeader,
CardTitle
} from "@/components/ui/card";
export default function Loading() { export default function Loading() {
return ( return (
<CardGrid maxCols={1}> <CardGrid maxCols={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

@ -8,47 +8,47 @@ import { querydb } from "./db";
import Loading from "./loading"; import Loading from "./loading";
export default function StatsPage() { export default function StatsPage() {
let [data, setData] = useState([]); let [data, setData] = useState([]);
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
const result = await querydb(); const result = await querydb();
// @ts-ignore // @ts-ignore
setData(result); setData(result);
}; };
fetchData(); fetchData();
}, []); }, []);
if (data.length !== 0 && data !== undefined && data !== null) { if (data.length !== 0 && data !== undefined && data !== null) {
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}`;
}; };
// @ts-ignore // @ts-ignore
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 data.length === 0 ? ( return data.length === 0 ? (
<Loading /> <Loading />
) : ( ) : (
<CardGrid maxCols={1}> <CardGrid maxCols={1}>
<Card> <Card>
{/* @ts-ignore */} {/* @ts-ignore */}
<DataTable columns={columns} data={data} /> <DataTable columns={columns} data={data} />
</Card> </Card>
</CardGrid> </CardGrid>
); );
} }

View file

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

View file

@ -4,35 +4,35 @@ import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
export default function ModeToggle() { export default function ModeToggle() {
const { setTheme } = useTheme(); const { setTheme } = useTheme();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" size="icon"> <Button variant="outline" size="icon">
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> <SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> <MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span> <span className="sr-only">Toggle theme</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}> <DropdownMenuItem onClick={() => setTheme("light")}>
Light Light
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}> <DropdownMenuItem onClick={() => setTheme("dark")}>
Dark Dark
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}> <DropdownMenuItem onClick={() => setTheme("system")}>
System System
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
} }

View file

@ -1,35 +1,35 @@
"use server"; "use server";
import postgres, { Sql } from 'postgres'; import postgres, { type Sql } from "postgres";
import { Surreal } from "surrealdb.js"; import { Surreal } from "surrealdb.js";
const db = new Surreal(); const db = new Surreal();
export async function initConnectionSurreal(): Promise<Surreal> { export async function initConnectionSurreal(): Promise<Surreal> {
try { try {
await db.connect("ws://" + process.env.DB_URL_PORT + "/rpc", { await db.connect(`ws://${process.env.DB_URL_PORT}/rpc`, {
namespace: "url", namespace: "url",
database: "url", database: "url",
auth: { auth: {
username: process.env.DB_USER || "root", username: process.env.DB_USER || "root",
password: process.env.DB_PASSWORD || "root", password: process.env.DB_PASSWORD || "root",
}, },
}); });
} catch (e) { } catch (e) {
console.error("ERROR", e); console.error("ERROR", e);
} }
return db; return db;
} }
export async function initConnectionPostgres(): Promise<Sql<{}>> { export async function initConnectionPostgres(): Promise<Sql<{}>> {
const DB_URL_PORT = (process.env.DB_URL_PORT || "postgres:5432").split(":"); const DB_URL_PORT = (process.env.DB_URL_PORT || "postgres:5432").split(":");
const sql = postgres({ const sql = postgres({
host: DB_URL_PORT[0], host: DB_URL_PORT[0],
port: parseInt(DB_URL_PORT[1]), port: Number.parseInt(DB_URL_PORT[1]),
user: process.env.POSTGRES_USER || "root", user: process.env.POSTGRES_USER || "root",
password: process.env.POSTGRES_PASSWORD || "root", password: process.env.POSTGRES_PASSWORD || "root",
database: process.env.POSTGRES_DB || "url", database: process.env.POSTGRES_DB || "url",
max: 100, max: 100,
onnotice: () => { }, onnotice: () => {},
}); });
return sql; return sql;
} }

View file

@ -3,28 +3,29 @@ import DarkModeToggle from "@/components/dark-mode-toggle";
import Link from "next/link"; import Link from "next/link";
export default async 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">
<Link href="/">Next Url Shortener</Link> <Link href="/">Next Url Shortener</Link>
</div> </div>
{/* <div className="transition-colors text-foreground/50 hover:text-foreground/100"> {/* <div className="transition-colors text-foreground/50 hover:text-foreground/100">
<Link href="/create">Create</Link> <Link href="/create">Create</Link>
</div> */} </div> */}
<div className="transition-colors text-foreground/50 hover:text-foreground/100"> <div className="transition-colors text-foreground/50 hover:text-foreground/100">
<Link href="/stats">Stats</Link> <Link href="/stats">Stats</Link>
</div> </div>
<div className="flex flex-1 items-center justify-between gap-4 md:justify-end">
<div className="transition-colors text-foreground/50 hover:text-foreground/100">
<Link href="https://github.com/NexVeridian/next-url-shortener">
GitHub
</Link>
</div>
<div className="flex flex-1 items-center justify-between gap-4 md:justify-end"> <DarkModeToggle />
<div className="transition-colors text-foreground/50 hover:text-foreground/100"> </div>
<Link href="https://github.com/NexVeridian/next-url-shortener">GitHub</Link> </nav>
</div> );
<DarkModeToggle />
</div>
</nav>
);
} }

View file

@ -1,7 +1,7 @@
"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";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) { export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
} }

View file

@ -1,6 +1,6 @@
import { type ClassValue, clsx } from "clsx" import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }

View file

@ -1,78 +1,78 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: ["class"], darkMode: ["class"],
content: [ content: [
'./pages/**/*.{ts,tsx}', "./pages/**/*.{ts,tsx}",
'./components/**/*.{ts,tsx}', "./components/**/*.{ts,tsx}",
'./app/**/*.{ts,tsx}', "./app/**/*.{ts,tsx}",
'./src/**/*.{ts,tsx}', "./src/**/*.{ts,tsx}",
], ],
theme: { theme: {
container: { container: {
center: true, center: true,
padding: "2rem", padding: "2rem",
screens: { screens: {
"3xl": "1536px", "3xl": "1536px",
"4xl": "1792px", "4xl": "1792px",
"5xl": "2048px", "5xl": "2048px",
}, },
}, },
extend: { extend: {
colors: { colors: {
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
background: "hsl(var(--background))", background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
primary: { primary: {
DEFAULT: "hsl(var(--primary))", DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))", foreground: "hsl(var(--primary-foreground))",
}, },
secondary: { secondary: {
DEFAULT: "hsl(var(--secondary))", DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))", foreground: "hsl(var(--secondary-foreground))",
}, },
destructive: { destructive: {
DEFAULT: "hsl(var(--destructive))", DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))", foreground: "hsl(var(--destructive-foreground))",
}, },
muted: { muted: {
DEFAULT: "hsl(var(--muted))", DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))", foreground: "hsl(var(--muted-foreground))",
}, },
accent: { accent: {
DEFAULT: "hsl(var(--accent))", DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))", foreground: "hsl(var(--accent-foreground))",
}, },
popover: { popover: {
DEFAULT: "hsl(var(--popover))", DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))", foreground: "hsl(var(--popover-foreground))",
}, },
card: { card: {
DEFAULT: "hsl(var(--card))", DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))", foreground: "hsl(var(--card-foreground))",
}, },
}, },
borderRadius: { borderRadius: {
lg: "var(--radius)", lg: "var(--radius)",
md: "calc(var(--radius) - 2px)", md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
}, },
keyframes: { keyframes: {
"accordion-down": { "accordion-down": {
from: { height: 0 }, from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" }, to: { height: "var(--radix-accordion-content-height)" },
}, },
"accordion-up": { "accordion-up": {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 }, to: { height: 0 },
}, },
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
}, },
}, },
}, },
plugins: [require("tailwindcss-animate")], plugins: [require("tailwindcss-animate")],
} };

View file

@ -1,40 +1,27 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom", "allowJs": true,
"dom.iterable", "skipLibCheck": true,
"esnext" "strict": true,
], "noEmit": true,
"allowJs": true, "esModuleInterop": true,
"skipLibCheck": true, "module": "esnext",
"strict": true, "moduleResolution": "bundler",
"noEmit": true, "resolveJsonModule": true,
"esModuleInterop": true, "isolatedModules": true,
"module": "esnext", "jsx": "preserve",
"moduleResolution": "bundler", "incremental": true,
"resolveJsonModule": true, "plugins": [
"isolatedModules": true, {
"jsx": "preserve", "name": "next"
"incremental": true, }
"plugins": [ ],
{ "paths": {
"name": "next" "@/*": ["./src/*"]
} }
], },
"paths": { "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"@/*": [ "exclude": ["node_modules"]
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
],
"exclude": [
"node_modules"
]
} }