Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Jan 1, 2025
1 parent c1a6b6d commit ef8e296
Show file tree
Hide file tree
Showing 17 changed files with 505 additions and 76 deletions.
20 changes: 9 additions & 11 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,14 @@ import { Hono } from "@/lib/app";
import { setupAuth } from "@/lib/auth";
import { apiReference } from "@scalar/hono-api-reference";
import { openAPISpecs } from "hono-openapi";
import { contextStorage } from "hono/context-storage";
import { cors } from "hono/cors";
import { sessionMiddleware } from "./middleware";
import router from "./routes";

const app = new Hono();

app.use(contextStorage());

app.route("/", router);
app.use("*", sessionMiddleware);

app.on(["POST", "GET"], "/api/auth/**", (c) => {
return setupAuth(c).handler(c.req.raw);
});

app.use(
"/api/auth/**",
"*",
cors({
origin: ["http://localhost:3000", "https://languine.ai"],
allowHeaders: ["Content-Type", "Authorization"],
Expand All @@ -30,6 +20,14 @@ app.use(
}),
);

app.use("*", sessionMiddleware);

app.on(["POST", "GET"], "/api/auth/**", (c) => {
return setupAuth(c).handler(c.req.raw);
});

app.route("/", router);

app.get(
"/openapi",
openAPISpecs(app, {
Expand Down
10 changes: 6 additions & 4 deletions apps/api/src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { db } from "@/db";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { bearer, emailOTP, organization } from "better-auth/plugins";
// import { resend } from "./resend";
import type { Context } from "hono";
import { resend } from "./resend";

export const setupAuth = (c: Context) => {
return betterAuth({
Expand All @@ -14,11 +14,13 @@ export const setupAuth = (c: Context) => {
secret: c.env.BETTER_AUTH_SECRET,
trustedOrigins: c.env.BETTER_AUTH_TRUSTED_ORIGINS.split(","),
secondaryStorage: {
get: (key) => c.env.KV.getItemRaw(`_auth:${key}`),
get: async (key) => {
return c.env.KV.get(`auth:${key}`);
},
set: (key, value, ttl) => {
return c.env.KV.set(`_auth:${key}`, value, { ttl });
return c.env.KV.put(`auth:${key}`, value, { ttl });
},
delete: (key) => c.env.KV.del(`_auth:${key}`),
delete: (key) => c.env.KV.delete(`auth:${key}`),
},
plugins: [
bearer(),
Expand Down
11 changes: 6 additions & 5 deletions apps/api/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { setupAuth } from "@/lib/auth";
import type { Context, Next } from "hono";
import { createMiddleware } from "hono/factory";

export const sessionMiddleware = async (c: Context, next: Next) => {
export const sessionMiddleware = createMiddleware(async (c, next) => {
const auth = setupAuth(c);
const session = await auth.api.getSession({ headers: c.req.raw.headers });
const headers = new Headers(c.req.raw.headers);
const session = await auth.api.getSession({ headers });

if (!session) {
c.set("user", null);
c.set("session", null);
return next();
return await next();
}

c.set("user", session.user);
c.set("session", session.session);

return next();
};
});
27 changes: 11 additions & 16 deletions apps/api/src/routes/users/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,19 @@ app.get(
},
}),
async (c) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");
const session = c.get("session");
const user = c.get("user");

if (!token) {
return c.json({ error: "No token provided" }, 401);
}
if (!user) return c.body(null, 401);

try {
// TODO: Implement user profile retrieval from token
return c.json({
data: {
email: "",
name: "",
provider: "github",
},
});
} catch (error) {
return c.json({ error: "Failed to get user profile" }, 401);
}
return c.json({
data: {
id: user.id,
email: user.email,
name: user.name,
activeOrganizationId: session.activeOrganizationId,
},
});
},
);

Expand Down
9 changes: 8 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@openpanel/nextjs": "^1.0.7",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"better-auth": "^1.1.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.469.0",
"next": "15.1.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^2.6.0"
"react-hook-form": "^7.54.2",
"tailwind-merge": "^2.6.0",
"zod": "^3.24.1"
},
"devDependencies": {
"typescript": "^5",
Expand Down
29 changes: 0 additions & 29 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,6 @@ export default function RootLayout({
disabled={process.env.NODE_ENV !== "production"}
/>
{children}

<div className="fixed bottom-4 right-4 flex items-center gap-2">
<a
href="https://dub.sh/lng"
target="_blank"
rel="noreferrer"
className="flex items-center gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
>
<g clipPath="url(#a)">
<path
fill="#F60"
d="M0 0v16h16V0H0Zm8.7 9.225v3.925H7.275V9.225L3.775 2.3h1.65L8 7.525 10.65 2.3h1.55L8.7 9.225Z"
/>
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h16v16H0z" />
</clipPath>
</defs>
</svg>
<span>Live on Hacker News</span>
</a>
</div>
</body>
</html>
);
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Login from "@/components/login";

export default function Page() {
return <Login />;
return (
<div className="min-h-screen flex items-center justify-center max-w-md mx-auto">
<Login />
</div>
);
}
67 changes: 63 additions & 4 deletions apps/web/src/components/login.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,77 @@
"use client";

import { authClient } from "@/lib/auth";
import Link from "next/link";
import { useState } from "react";
import { Logo } from "./logo";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { InputOTPGroup } from "./ui/input-otp";
import { InputOTP } from "./ui/input-otp";
import { InputOTPSlot } from "./ui/input-otp";

export default function Login() {
const handleLogin = async () => {
await authClient.emailOtp.sendVerificationOtp({
email: "[email protected]",
email,
type: "sign-in",
});
};

const [email, setEmail] = useState("");
const [showOTP, setShowOTP] = useState(false);
const [otp, setOtp] = useState("");

return (
<button onClick={handleLogin} type="button">
Login
</button>
<div className="flex flex-col gap-4 w-full">
<div className="mb-4">
<Link href="/">
<Logo />
</Link>
</div>

{!showOTP ? (
<form
className="flex flex-col gap-4"
onSubmit={async (e) => {
e.preventDefault();
await handleLogin();
setShowOTP(true);
}}
>
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<Button type="submit">Send code</Button>
</form>
) : (
<form
className="flex flex-col gap-4"
onSubmit={async (e) => {
e.preventDefault();
await authClient.signIn.emailOtp({
email,
otp,
});
}}
>
<InputOTP maxLength={6} value={otp} onChange={setOtp}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<Button type="submit">Sign in</Button>
</form>
)}
</div>
);
}
10 changes: 5 additions & 5 deletions apps/web/src/components/logo.tsx

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions apps/web/src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
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 px-3",
lg: "h-11 px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = "Button";

export { Button, buttonVariants };
Loading

0 comments on commit ef8e296

Please sign in to comment.