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..6172667
Binary files /dev/null and b/bun.lockb differ
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..5f3a8c0
--- /dev/null
+++ b/kirimase.config.json
@@ -0,0 +1,16 @@
+{
+ "hasSrc": true,
+ "packages": [
+ "drizzle"
+ ],
+ "preferredPackageManager": "bun",
+ "t3": false,
+ "alias": "@",
+ "analytics": true,
+ "rootPath": "src/",
+ "componentLib": null,
+ "driver": "pg",
+ "provider": "postgresjs",
+ "orm": "drizzle",
+ "auth": null
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 233f80e..dcfa8fd 100644
--- a/package.json
+++ b/package.json
@@ -6,22 +6,39 @@
"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": {
+ "@t3-oss/env-nextjs": "^0.9.2",
+ "drizzle-orm": "^0.30.2",
+ "drizzle-zod": "^0.5.1",
+ "lucide-react": "^0.358.0",
+ "nanoid": "^5.0.6",
+ "next": "14.1.3",
+ "postgres": "^3.4.3",
"react": "^18",
"react-dom": "^18",
- "next": "14.1.3"
+ "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..3593884
--- /dev/null
+++ b/src/app/(app)/layout.tsx
@@ -0,0 +1,15 @@
+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/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/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..59f9bed
--- /dev/null
+++ b/src/components/Navbar.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { usePathname } from "next/navigation";
+
+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..7f70867
--- /dev/null
+++ b/src/components/SidebarItems.tsx
@@ -0,0 +1,90 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
+import { LucideIcon } from "lucide-react";
+
+
+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/config/nav.ts b/src/config/nav.ts
new file mode 100644
index 0000000..9127c48
--- /dev/null
+++ b/src/config/nav.ts
@@ -0,0 +1,13 @@
+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 },
+];
+
+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..d9c327c
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,2 @@
+import { customAlphabet } from "nanoid";
+export const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789");
\ No newline at end of file
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e9a0944..4767d43 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -8,6 +8,41 @@ const config: Config = {
],
theme: {
extend: {
+ 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))",
+ },
+ },
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
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