diff --git a/apps/frontend/components.json b/apps/frontend/components.json new file mode 100644 index 0000000..e60cc81 --- /dev/null +++ b/apps/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/apps/frontend/package.json b/apps/frontend/package.json index e8ca69e..217bc2c 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -11,13 +11,21 @@ }, "dependencies": { "@antfu/ni": "^23.3.1", + "@radix-ui/react-slot": "^1.1.2", "@tailwindcss/vite": "^4.0.6", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.475.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "tailwindcss": "^4.0.6" + "shadcn": "^2.4.0-canary.6", + "tailwind-merge": "^3.0.1", + "tailwindcss": "^4.0.6", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@eslint/js": "^9.19.0", + "@types/node": "^22.13.4", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react-swc": "^3.5.0", diff --git a/apps/frontend/src/components/ui/button.tsx b/apps/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..61875fc --- /dev/null +++ b/apps/frontend/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +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 gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/apps/frontend/src/lib/utils.ts b/apps/frontend/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/apps/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/apps/frontend/src/styles.css b/apps/frontend/src/styles.css index f1d8c73..0a1f462 100644 --- a/apps/frontend/src/styles.css +++ b/apps/frontend/src/styles.css @@ -1 +1,100 @@ @import "tailwindcss"; + +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: hsl(0 0% 100%); + --foreground: hsl(0 0% 3.9%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(0 0% 3.9%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(0 0% 3.9%); + --primary: hsl(0 0% 9%); + --primary-foreground: hsl(0 0% 98%); + --secondary: hsl(0 0% 96.1%); + --secondary-foreground: hsl(0 0% 9%); + --muted: hsl(0 0% 96.1%); + --muted-foreground: hsl(0 0% 45.1%); + --accent: hsl(0 0% 96.1%); + --accent-foreground: hsl(0 0% 9%); + --destructive: hsl(0 84.2% 60.2%); + --destructive-foreground: hsl(0 0% 98%); + --border: hsl(0 0% 89.8%); + --input: hsl(0 0% 89.8%); + --ring: hsl(0 0% 3.9%); + --chart-1: hsl(12 76% 61%); + --chart-2: hsl(173 58% 39%); + --chart-3: hsl(197 37% 24%); + --chart-4: hsl(43 74% 66%); + --chart-5: hsl(27 87% 67%); + --radius: 0.6rem; +} + +.dark { + --background: hsl(0 0% 3.9%); + --foreground: hsl(0 0% 98%); + --card: hsl(0 0% 3.9%); + --card-foreground: hsl(0 0% 98%); + --popover: hsl(0 0% 3.9%); + --popover-foreground: hsl(0 0% 98%); + --primary: hsl(0 0% 98%); + --primary-foreground: hsl(0 0% 9%); + --secondary: hsl(0 0% 14.9%); + --secondary-foreground: hsl(0 0% 98%); + --muted: hsl(0 0% 14.9%); + --muted-foreground: hsl(0 0% 63.9%); + --accent: hsl(0 0% 14.9%); + --accent-foreground: hsl(0 0% 98%); + --destructive: hsl(0 62.8% 30.6%); + --destructive-foreground: hsl(0 0% 98%); + --border: hsl(0 0% 14.9%); + --input: hsl(0 0% 14.9%); + --ring: hsl(0 0% 83.1%); + --chart-1: hsl(220 70% 50%); + --chart-2: hsl(160 60% 45%); + --chart-3: hsl(30 80% 55%); + --chart-4: hsl(280 65% 60%); + --chart-5: hsl(340 75% 55%); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index 1ffef60..fec8c8e 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -3,5 +3,11 @@ "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } - ] + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } } diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 963c990..292f489 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -2,6 +2,10 @@ import { Hono } from "hono"; import { serveStatic } from "hono/bun"; import { logger } from "hono/logger"; import { ocmeService } from "./services/ocmeServer"; +import { AuthConfig } from "@auth/core/types"; +import { authHandler, initAuthConfig, verifyAuth } from "@hono/auth-js"; +import Credentials from "@auth/core/providers/credentials"; +import { authConfig } from "./auth/auth"; //import { expensesRoute } from "./routes/expenses"; const app = new Hono(); @@ -13,9 +17,39 @@ app.all("/ocme/*", async (c) => { return ocmeService(c); }); -app.get("/test", (c) => { +//auth stuff +app.use("*", initAuthConfig(authConfig)); + +app.use("/api/auth/*", async (c, next) => { + const response = await authHandler()(c, next); + + if (c.req.path === "/api/auth/callback/credentials") { + const setCookieHeader = response.headers.get("Set-Cookie"); + + if (setCookieHeader) { + const tokenMatch = setCookieHeader.match(/authjs\.session-token=([^;]+)/); + const jwt = tokenMatch ? tokenMatch[1] : null; + + if (jwt) { + console.log("Extracted JWT:", jwt); + return c.json({ token: jwt }); + } + } + } + + return response; +}); + +app.get("/api/protected", verifyAuth(), (c) => { + const auth = c.get("authUser"); + return c.json(auth); +}); + +app.get("/api/test", (c) => { + const auth = c.get("authUser"); return c.json({ success: true, message: "hello from bun" }); }); +// const authRoute = app.basePath("/api/auth").route("*", ) //const apiRoute = app.basePath("/api").route("/expenses", expensesRoute); diff --git a/apps/server/src/auth/auth.ts b/apps/server/src/auth/auth.ts new file mode 100644 index 0000000..c86b632 --- /dev/null +++ b/apps/server/src/auth/auth.ts @@ -0,0 +1,54 @@ +import { Context } from "hono"; +import { authHandler, initAuthConfig, verifyAuth } from "@hono/auth-js"; +import Credentials from "@auth/core/providers/credentials"; +import { AuthConfig } from "@auth/core/types"; + +export const authConfig: AuthConfig = { + secret: process.env.AUTH_SECRET, + providers: [ + Credentials({ + name: "Credentials", + credentials: { + username: { label: "Username", type: "text" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + // Add your authentication logic here + const user = { id: "1", name: "John Doe", email: "john@example.com" }; + if ( + credentials?.username === "john" && + credentials?.password === "password" + ) { + return user; + } + return null; + }, + }), + ], + session: { + strategy: "jwt", + }, + callbacks: { + // async session({ session, token }) { + // session.user.id = token.sub; + // return session; + // }, + async jwt({ token, user }) { + if (user) { + token.sub = user.id; + } + return token; + }, + }, +}; + +// auth.use("/api/auth/*", authHandler()); + +// auth.use("/api/*", verifyAuth()); + +// auth.get("/api/protected", (c) => { +// const auth = c.get("authUser"); +// return c.json(auth); +// }); + +// export default auth; diff --git a/package.json b/package.json index a333b31..d6eea2a 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,18 @@ "author": "", "license": "ISC", "dependencies": { + "@auth/core": "^0.37.4", "@dotenvx/dotenvx": "^1.35.0", + "@hono/auth-js": "^1.0.15", + "@hono/zod-openapi": "^0.18.4", + "@shared/lib": "*", "@types/bun": "^1.2.2", "concurrently": "^9.1.2", "cors": "^2.8.5", "dotenv": "^16.4.7", - "hono": "^4.7.0", + "hono": "^4.7.1", "http-proxy-middleware": "^3.0.3", - "@shared/lib": "*" + "zod": "^3.24.2" }, "peerDependencies": { "typescript": "^5.0.0"