diff --git a/.env b/.env
new file mode 100644
index 0000000..498ab17
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+DATABASE_URL=postgres://postgres:postgres@localhost:5432/{DB_NAME}
\ No newline at end of file
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..773bb37
Binary files /dev/null and b/bun.lockb differ
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..33fb800
--- /dev/null
+++ b/components.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..2175e39
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,11 @@
+import type { Config } from "drizzle-kit";
+import { env } from "@/lib/env.mjs";
+
+export default {
+ schema: "./src/lib/db/schema",
+ out: "./src/lib/db/migrations",
+ driver: "pg",
+ dbCredentials: {
+ connectionString: env.DATABASE_URL,
+ }
+} satisfies Config;
\ No newline at end of file
diff --git a/kirimase.config.json b/kirimase.config.json
new file mode 100644
index 0000000..ca52dc2
--- /dev/null
+++ b/kirimase.config.json
@@ -0,0 +1,17 @@
+{
+ "hasSrc": true,
+ "packages": [
+ "shadcn-ui",
+ "drizzle"
+ ],
+ "preferredPackageManager": "bun",
+ "t3": false,
+ "alias": "@",
+ "analytics": true,
+ "rootPath": "src/",
+ "componentLib": "shadcn-ui",
+ "driver": "pg",
+ "provider": "postgresjs",
+ "orm": "drizzle",
+ "auth": null
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 233f80e..e9ee847 100644
--- a/package.json
+++ b/package.json
@@ -6,22 +6,49 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "db:generate": "drizzle-kit generate:pg",
+ "db:migrate": "tsx src/lib/db/migrate.ts",
+ "db:drop": "drizzle-kit drop",
+ "db:pull": "drizzle-kit introspect:pg",
+ "db:studio": "drizzle-kit studio",
+ "db:check": "drizzle-kit check:pg"
},
"dependencies": {
+ "@radix-ui/react-avatar": "^1.0.4",
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
+ "@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-slot": "^1.0.2",
+ "@t3-oss/env-nextjs": "^0.9.2",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.0",
+ "drizzle-orm": "^0.30.2",
+ "drizzle-zod": "^0.5.1",
+ "lucide-react": "^0.358.0",
+ "nanoid": "^5.0.6",
+ "next": "14.1.3",
+ "next-themes": "^0.3.0",
+ "postgres": "^3.4.3",
"react": "^18",
"react-dom": "^18",
- "next": "14.1.3"
+ "sonner": "^1.4.3",
+ "tailwind-merge": "^2.2.2",
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.22.4"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
+ "dotenv": "^16.4.5",
+ "drizzle-kit": "^0.20.14",
+ "eslint": "^8",
+ "eslint-config-next": "14.1.3",
+ "pg": "^8.11.3",
"postcss": "^8",
"tailwindcss": "^3.3.0",
- "eslint": "^8",
- "eslint-config-next": "14.1.3"
+ "tsx": "^4.7.1",
+ "typescript": "^5"
}
-}
+}
\ No newline at end of file
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx
new file mode 100644
index 0000000..13149b0
--- /dev/null
+++ b/src/app/(app)/dashboard/page.tsx
@@ -0,0 +1,10 @@
+export default function Home() {
+ return (
+
+ Home
+
+ Wow, that was easy. Now it's your turn. Building something cool!
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
new file mode 100644
index 0000000..485fd9e
--- /dev/null
+++ b/src/app/(app)/layout.tsx
@@ -0,0 +1,18 @@
+import { Toaster } from "@/components/ui/sonner";
+import Navbar from "@/components/Navbar";
+import Sidebar from "@/components/Sidebar";
+export default async function AppLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+{children}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(app)/settings/page.tsx b/src/app/(app)/settings/page.tsx
new file mode 100644
index 0000000..a801d9e
--- /dev/null
+++ b/src/app/(app)/settings/page.tsx
@@ -0,0 +1,106 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import { useTheme } from "next-themes";
+
+export default function Page() {
+ const { setTheme } = useTheme();
+ return (
+
+
Settings
+
+
+
Appearance
+
+ Customize the appearance of the app. Automatically switch between
+ day and night themes.
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index 875c01e..77d3522 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,33 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
-:root {
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-}
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
}
}
-
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
-}
-
-@layer utilities {
- .text-balance {
- text-wrap: balance;
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
}
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3314e47..03f7b34 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
+import { ThemeProvider } from "@/components/ThemeProvider";
const inter = Inter({ subsets: ["latin"] });
@@ -16,7 +17,9 @@ export default function RootLayout({
}>) {
return (
- {children}
+
+{children}
+
);
}
diff --git a/src/app/loading.tsx b/src/app/loading.tsx
new file mode 100644
index 0000000..568eb6e
--- /dev/null
+++ b/src/app/loading.tsx
@@ -0,0 +1,25 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index b81507d..0a69b41 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,113 +1,183 @@
-import Image from "next/image";
+/**
+ * v0 by Vercel.
+ * @see https://v0.dev/t/PmwTvNfrVgf
+ * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
+ */
+import Link from "next/link";
-export default function Home() {
+export default function LandingPage() {
return (
-
-
-
- Get started by editing
- src/app/page.tsx
-
-
-
-
-
-
-
-
-
+ );
+}
-
-
- Deploy{" "}
-
- ->
-
-
-
- Instantly deploy your Next.js site to a shareable URL with Vercel.
-
-
-
-
+function MountainIcon(props: any) {
+ return (
+
);
}
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
new file mode 100644
index 0000000..26b5257
--- /dev/null
+++ b/src/components/Navbar.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { usePathname } from "next/navigation";
+
+import { Button } from "@/components/ui/button";
+
+import { AlignRight } from "lucide-react";
+import { defaultLinks } from "@/config/nav";
+
+export default function Navbar() {
+ const [open, setOpen] = useState(false);
+ const pathname = usePathname();
+ return (
+
+
+ {open ? (
+
+
+ {defaultLinks.map((link) => (
+ - setOpen(false)} className="">
+
+ {link.title}
+
+
+ ))}
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
new file mode 100644
index 0000000..df3925a
--- /dev/null
+++ b/src/components/Sidebar.tsx
@@ -0,0 +1,16 @@
+import SidebarItems from "./SidebarItems";
+
+const Sidebar = () => {
+ return (
+
+ );
+};
+
+export default Sidebar;
diff --git a/src/components/SidebarItems.tsx b/src/components/SidebarItems.tsx
new file mode 100644
index 0000000..7dea48b
--- /dev/null
+++ b/src/components/SidebarItems.tsx
@@ -0,0 +1,91 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
+import { LucideIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { defaultLinks, additionalLinks } from "@/config/nav";
+
+export interface SidebarLink {
+ title: string;
+ href: string;
+ icon: LucideIcon;
+}
+
+const SidebarItems = () => {
+ return (
+ <>
+
+ {additionalLinks.length > 0
+ ? additionalLinks.map((l) => (
+
+ ))
+ : null}
+ >
+ );
+};
+export default SidebarItems;
+
+const SidebarLinkGroup = ({
+ links,
+ title,
+ border,
+}: {
+ links: SidebarLink[];
+ title?: string;
+ border?: boolean;
+}) => {
+ const fullPathname = usePathname();
+ const pathname = "/" + fullPathname.split("/")[1];
+
+ return (
+
+ {title ? (
+
+ {title}
+
+ ) : null}
+
+ {links.map((link) => (
+ -
+
+
+ ))}
+
+
+ );
+};
+const SidebarLink = ({
+ link,
+ active,
+}: {
+ link: SidebarLink;
+ active: boolean;
+}) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx
new file mode 100644
index 0000000..b0ff266
--- /dev/null
+++ b/src/components/ThemeProvider.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import { type ThemeProviderProps } from "next-themes/dist/types";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children};
+}
diff --git a/src/components/ui/ThemeToggle.tsx b/src/components/ui/ThemeToggle.tsx
new file mode 100644
index 0000000..63ff415
--- /dev/null
+++ b/src/components/ui/ThemeToggle.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import * as React from "react";
+import { MoonIcon, SunIcon } from "lucide-react";
+import { useTheme } from "next-themes";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+export function ModeToggle() {
+ const { setTheme } = useTheme();
+
+ return (
+
+
+
+
+
+ setTheme("light")}>
+ Light
+
+ setTheme("dark")}>
+ Dark
+
+ setTheme("system")}>
+ System
+
+
+
+ );
+}
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..51e507b
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..0ba4277
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..f69a0d6
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,200 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..677d05f
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..5341821
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
new file mode 100644
index 0000000..452f4d9
--- /dev/null
+++ b/src/components/ui/sonner.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import { useTheme } from "next-themes"
+import { Toaster as Sonner } from "sonner"
+
+type ToasterProps = React.ComponentProps
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+
+ )
+}
+
+export { Toaster }
diff --git a/src/config/nav.ts b/src/config/nav.ts
new file mode 100644
index 0000000..380f9a7
--- /dev/null
+++ b/src/config/nav.ts
@@ -0,0 +1,14 @@
+import { SidebarLink } from "@/components/SidebarItems";
+import { Cog, Globe, HomeIcon } from "lucide-react";
+
+type AdditionalLinks = {
+ title: string;
+ links: SidebarLink[];
+};
+
+export const defaultLinks: SidebarLink[] = [
+ { href: "/dashboard", title: "Home", icon: HomeIcon },
+ { href: "/settings", title: "Settings", icon: Cog },
+];
+
+export const additionalLinks: AdditionalLinks[] = [];
diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts
new file mode 100644
index 0000000..d0c05ff
--- /dev/null
+++ b/src/lib/db/index.ts
@@ -0,0 +1,6 @@
+import { drizzle } from "drizzle-orm/postgres-js";
+import postgres from "postgres";
+import { env } from "@/lib/env.mjs";
+
+export const client = postgres(env.DATABASE_URL);
+export const db = drizzle(client);
\ No newline at end of file
diff --git a/src/lib/db/migrate.ts b/src/lib/db/migrate.ts
new file mode 100644
index 0000000..03b3bb6
--- /dev/null
+++ b/src/lib/db/migrate.ts
@@ -0,0 +1,36 @@
+import { env } from "@/lib/env.mjs";
+
+import { drizzle } from "drizzle-orm/postgres-js";
+import { migrate } from "drizzle-orm/postgres-js/migrator";
+import postgres from "postgres";
+
+
+const runMigrate = async () => {
+ if (!env.DATABASE_URL) {
+ throw new Error("DATABASE_URL is not defined");
+ }
+
+
+const connection = postgres(env.DATABASE_URL, { max: 1 });
+
+const db = drizzle(connection);
+
+
+ console.log("⏳ Running migrations...");
+
+ const start = Date.now();
+
+ await migrate(db, { migrationsFolder: 'src/lib/db/migrations' });
+
+ const end = Date.now();
+
+ console.log("✅ Migrations completed in", end - start, "ms");
+
+ process.exit(0);
+};
+
+runMigrate().catch((err) => {
+ console.error("❌ Migration failed");
+ console.error(err);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/src/lib/env.mjs b/src/lib/env.mjs
new file mode 100644
index 0000000..6a0a110
--- /dev/null
+++ b/src/lib/env.mjs
@@ -0,0 +1,24 @@
+import { createEnv } from "@t3-oss/env-nextjs";
+import { z } from "zod";
+
+export const env = createEnv({
+ server: {
+ NODE_ENV: z
+ .enum(["development", "test", "production"])
+ .default("development"),
+ DATABASE_URL: z.string().min(1),
+
+ },
+ client: {
+ // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1),
+ },
+ // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually
+ // runtimeEnv: {
+ // DATABASE_URL: process.env.DATABASE_URL,
+ // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
+ // },
+ // For Next.js >= 13.4.4, you only need to destructure client variables:
+ experimental__runtimeEnv: {
+ // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
+ },
+});
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..199deca
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,9 @@
+import { customAlphabet } from "nanoid";
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
+
+export const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789");
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e9a0944..061375e 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,20 +1,76 @@
-import type { Config } from "tailwindcss";
+const { fontFamily } = require("tailwindcss/defaultTheme")
-const config: Config = {
- content: [
- "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
- ],
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: ["class"],
+ content: ["src/app/**/*.{ts,tsx}", "src/components/**/*.{ts,tsx}"],
theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
extend: {
- backgroundImage: {
- "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
- "gradient-conic":
- "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: `var(--radius)`,
+ md: `calc(var(--radius) - 2px)`,
+ sm: "calc(var(--radius) - 4px)",
+ },
+ fontFamily: {
+ sans: ["var(--font-sans)", ...fontFamily.sans],
+ },
+ keyframes: {
+ "accordion-down": {
+ from: { height: 0 },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: 0 },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
},
},
},
- plugins: [],
-};
-export default config;
+ plugins: [require("tailwindcss-animate")],
+}
diff --git a/tsconfig.json b/tsconfig.json
index 7b28589..c5a7b07 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,10 @@
{
"compilerOptions": {
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -18,9 +22,20 @@
}
],
"paths": {
- "@/*": ["./src/*"]
- }
+ "@/*": [
+ "./src/*"
+ ]
+ },
+ "target": "esnext",
+ "baseUrl": "./"
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
-}
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
\ No newline at end of file