diff --git a/src/app.tsx b/src/app.tsx index 167e33e..c1562d0 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -23,8 +23,6 @@ function AppLayout(props: { children: any }) { centerWidth, leftBarVisible, rightBarVisible, - toggleLeftBar, - toggleRightBar, setLeftBarVisible, setRightBarVisible, barsInitialized diff --git a/src/components/Bars.tsx b/src/components/Bars.tsx index 54cf0a6..457dc27 100644 --- a/src/components/Bars.tsx +++ b/src/components/Bars.tsx @@ -156,6 +156,18 @@ export function LeftBar() { } | null>(null); const [isMounted, setIsMounted] = createSignal(false); + const [signOutLoading, setSignOutLoading] = createSignal(false); + + const handleSignOut = async () => { + setSignOutLoading(true); + try { + await api.auth.signOut.mutate(); + window.location.href = "/"; + } catch (error) { + console.error("Sign out failed:", error); + setSignOutLoading(false); + } + }; onMount(async () => { // Mark as mounted to avoid hydration mismatch @@ -416,6 +428,17 @@ export function LeftBar() { + +
  • + +
  • +
    diff --git a/src/context/bars.tsx b/src/context/bars.tsx index 7146dcb..49063cc 100644 --- a/src/context/bars.tsx +++ b/src/context/bars.tsx @@ -13,8 +13,6 @@ const BarsContext = createContext<{ setLeftBarVisible: (visible: boolean) => void; rightBarVisible: Accessor; setRightBarVisible: (visible: boolean) => void; - toggleLeftBar: () => void; - toggleRightBar: () => void; barsInitialized: Accessor; }>({ leftBarSize: () => 0, @@ -27,8 +25,6 @@ const BarsContext = createContext<{ setLeftBarVisible: () => {}, rightBarVisible: () => true, setRightBarVisible: () => {}, - toggleLeftBar: () => {}, - toggleRightBar: () => {}, barsInitialized: () => false }); @@ -112,10 +108,6 @@ export function BarsProvider(props: { children: any }) { hapticFeedback(50); _setRightBarVisible(visible); }; - - const toggleLeftBar = () => setLeftBarVisible(!leftBarVisible()); - const toggleRightBar = () => setRightBarVisible(!rightBarVisible()); - return ( diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 21cd9dc..9fec329 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -6,7 +6,6 @@ export default function Home() {
    Hey!
    -
    My name is Mike Freno, I'm a{" "} @@ -15,10 +14,16 @@ export default function Home() {
    - I'm a passionate dev tooling and game developer, recently been working - in the world of Love2D and you can see some of my work here: - I'm a huge lover of open source software, and + I'm a passionate dev tooling, game, and open source software developer. + Recently been working in the world of{" "} + + LÖVE + {" "} + You can see some of my work here(github)
    My Collection of By-the-ways:
    diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 67463ab..e49af3d 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -9,7 +9,10 @@ import { setCookie, getCookie } from "vinxi/http"; import type { User } from "~/types/user"; // Helper to create JWT token -async function createJWT(userId: string, expiresIn: string = "14d"): Promise { +async function createJWT( + userId: string, + expiresIn: string = "14d" +): Promise { const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const token = await new SignJWT({ id: userId }) .setProtectedHeader({ alg: "HS256" }) @@ -26,11 +29,11 @@ async function sendEmail(to: string, subject: string, htmlContent: string) { const sendinblueData = { sender: { name: "freno.me", - email: "no_reply@freno.me", + email: "no_reply@freno.me" }, to: [{ email: to }], htmlContent, - subject, + subject }; const response = await fetch(apiUrl, { @@ -38,9 +41,9 @@ async function sendEmail(to: string, subject: string, htmlContent: string) { headers: { accept: "application/json", "api-key": apiKey, - "content-type": "application/json", + "content-type": "application/json" }, - body: JSON.stringify(sendinblueData), + body: JSON.stringify(sendinblueData) }); if (!response.ok) { @@ -65,22 +68,22 @@ export const authRouter = createTRPCRouter({ method: "POST", headers: { "Content-Type": "application/json", - Accept: "application/json", + Accept: "application/json" }, body: JSON.stringify({ - client_id: env.VITE_GITHUB_CLIENT_ID || env.NEXT_PUBLIC_GITHUB_CLIENT_ID, + client_id: env.VITE_GITHUB_CLIENT_ID, client_secret: env.GITHUB_CLIENT_SECRET, - code, - }), - }, + code + }) + } ); const { access_token } = await tokenResponse.json(); // Fetch user info from GitHub const userResponse = await fetch("https://api.github.com/user", { headers: { - Authorization: `token ${access_token}`, - }, + Authorization: `token ${access_token}` + } }); const user = await userResponse.json(); @@ -117,18 +120,18 @@ export const authRouter = createTRPCRouter({ path: "/", httpOnly: true, secure: env.NODE_ENV === "production", - sameSite: "lax", + sameSite: "lax" }); return { success: true, - redirectTo: "/account", + redirectTo: "/account" }; } catch (error) { console.error("GitHub authentication failed:", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "GitHub authentication failed", + message: "GitHub authentication failed" }); } }), @@ -141,19 +144,22 @@ export const authRouter = createTRPCRouter({ try { // Exchange code for access token - const tokenResponse = await fetch("https://oauth2.googleapis.com/token", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - code: code, - client_id: env.VITE_GOOGLE_CLIENT_ID || env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "", - client_secret: env.GOOGLE_CLIENT_SECRET, - redirect_uri: `${env.VITE_DOMAIN || env.NEXT_PUBLIC_DOMAIN}/api/auth/callback/google`, - grant_type: "authorization_code", - }), - }); + const tokenResponse = await fetch( + "https://oauth2.googleapis.com/token", + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams({ + code: code, + client_id: env.VITE_GOOGLE_CLIENT_ID || "", + client_secret: env.GOOGLE_CLIENT_SECRET, + redirect_uri: `${env.VITE_DOMAIN || "https://freno.me"}/api/auth/callback/google`, + grant_type: "authorization_code" + }) + } + ); const { access_token } = await tokenResponse.json(); @@ -162,9 +168,9 @@ export const authRouter = createTRPCRouter({ "https://www.googleapis.com/oauth2/v3/userinfo", { headers: { - Authorization: `Bearer ${access_token}`, - }, - }, + Authorization: `Bearer ${access_token}` + } + } ); const userData = await userResponse.json(); @@ -196,11 +202,11 @@ export const authRouter = createTRPCRouter({ email_verified, name, "google", - image, + image ]; await conn.execute({ sql: insertQuery, - args: insertParams, + args: insertParams }); } @@ -213,18 +219,18 @@ export const authRouter = createTRPCRouter({ path: "/", httpOnly: true, secure: env.NODE_ENV === "production", - sameSite: "lax", + sameSite: "lax" }); return { success: true, - redirectTo: "/account", + redirectTo: "/account" }; } catch (error) { console.error("Google authentication failed:", error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", - message: "Google authentication failed", + message: "Google authentication failed" }); } }), @@ -235,8 +241,8 @@ export const authRouter = createTRPCRouter({ z.object({ email: z.string().email(), token: z.string(), - rememberMe: z.boolean().optional(), - }), + rememberMe: z.boolean().optional() + }) ) .mutation(async ({ input, ctx }) => { const { email, token, rememberMe } = input; @@ -250,7 +256,7 @@ export const authRouter = createTRPCRouter({ if (payload.email !== email) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Email mismatch", + message: "Email mismatch" }); } @@ -262,7 +268,7 @@ export const authRouter = createTRPCRouter({ if (!res.rows[0]) { throw new TRPCError({ code: "NOT_FOUND", - message: "User not found", + message: "User not found" }); } @@ -276,7 +282,7 @@ export const authRouter = createTRPCRouter({ path: "/", httpOnly: true, secure: env.NODE_ENV === "production", - sameSite: "lax", + sameSite: "lax" }; if (rememberMe) { @@ -284,11 +290,16 @@ export const authRouter = createTRPCRouter({ } // If rememberMe is false, cookie will be session-only (no maxAge) - setCookie(ctx.event.nativeEvent, "userIDToken", userToken, cookieOptions); + setCookie( + ctx.event.nativeEvent, + "userIDToken", + userToken, + cookieOptions + ); return { success: true, - redirectTo: "/account", + redirectTo: "/account" }; } catch (error) { if (error instanceof TRPCError) { @@ -297,7 +308,7 @@ export const authRouter = createTRPCRouter({ console.error("Email login failed:", error); throw new TRPCError({ code: "UNAUTHORIZED", - message: "Authentication failed", + message: "Authentication failed" }); } }), @@ -307,8 +318,8 @@ export const authRouter = createTRPCRouter({ .input( z.object({ email: z.string().email(), - token: z.string(), - }), + token: z.string() + }) ) .mutation(async ({ input }) => { const { email, token } = input; @@ -322,7 +333,7 @@ export const authRouter = createTRPCRouter({ if (payload.email !== email) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "Email mismatch", + message: "Email mismatch" }); } @@ -333,7 +344,7 @@ export const authRouter = createTRPCRouter({ return { success: true, - message: "Email verification success, you may close this window", + message: "Email verification success, you may close this window" }; } catch (error) { if (error instanceof TRPCError) { @@ -342,7 +353,7 @@ export const authRouter = createTRPCRouter({ console.error("Email verification failed:", error); throw new TRPCError({ code: "UNAUTHORIZED", - message: "Invalid token", + message: "Invalid token" }); } }), @@ -353,8 +364,8 @@ export const authRouter = createTRPCRouter({ z.object({ email: z.string().email(), password: z.string().min(8), - passwordConfirmation: z.string().min(8), - }), + passwordConfirmation: z.string().min(8) + }) ) .mutation(async ({ input, ctx }) => { const { email, password, passwordConfirmation } = input; @@ -362,7 +373,7 @@ export const authRouter = createTRPCRouter({ if (password !== passwordConfirmation) { throw new TRPCError({ code: "BAD_REQUEST", - message: "passwordMismatch", + message: "passwordMismatch" }); } @@ -373,7 +384,7 @@ export const authRouter = createTRPCRouter({ try { await conn.execute({ sql: "INSERT INTO User (id, email, password_hash, provider) VALUES (?, ?, ?, ?)", - args: [userId, email, passwordHash, "email"], + args: [userId, email, passwordHash, "email"] }); // Create JWT token @@ -385,7 +396,7 @@ export const authRouter = createTRPCRouter({ path: "/", httpOnly: true, secure: env.NODE_ENV === "production", - sameSite: "lax", + sameSite: "lax" }); return { success: true, message: "success" }; @@ -393,7 +404,7 @@ export const authRouter = createTRPCRouter({ console.error("Registration error:", e); throw new TRPCError({ code: "BAD_REQUEST", - message: "duplicate", + message: "duplicate" }); } }), @@ -404,8 +415,8 @@ export const authRouter = createTRPCRouter({ z.object({ email: z.string().email(), password: z.string(), - rememberMe: z.boolean().optional(), - }), + rememberMe: z.boolean().optional() + }) ) .mutation(async ({ input, ctx }) => { const { email, password, rememberMe } = input; @@ -413,13 +424,13 @@ export const authRouter = createTRPCRouter({ const conn = ConnectionFactory(); const res = await conn.execute({ sql: "SELECT * FROM User WHERE email = ? AND provider = ?", - args: [email, "email"], + args: [email, "email"] }); if (res.rows.length === 0) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "no-match", + message: "no-match" }); } @@ -428,7 +439,7 @@ export const authRouter = createTRPCRouter({ if (!user.password_hash) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "no-match", + message: "no-match" }); } @@ -437,7 +448,7 @@ export const authRouter = createTRPCRouter({ if (!passwordMatch) { throw new TRPCError({ code: "UNAUTHORIZED", - message: "no-match", + message: "no-match" }); } @@ -450,7 +461,7 @@ export const authRouter = createTRPCRouter({ path: "/", httpOnly: true, secure: env.NODE_ENV === "production", - sameSite: "lax", + sameSite: "lax" }; if (rememberMe) { @@ -467,21 +478,24 @@ export const authRouter = createTRPCRouter({ .input( z.object({ email: z.string().email(), - rememberMe: z.boolean().optional(), - }), + rememberMe: z.boolean().optional() + }) ) .mutation(async ({ input, ctx }) => { const { email, rememberMe } = input; // Check rate limiting - const requested = getCookie(ctx.event.nativeEvent, "emailLoginLinkRequested"); + const requested = getCookie( + ctx.event.nativeEvent, + "emailLoginLinkRequested" + ); if (requested) { const expires = new Date(requested); const remaining = expires.getTime() - Date.now(); if (remaining > 0) { throw new TRPCError({ code: "TOO_MANY_REQUESTS", - message: "countdown not expired", + message: "countdown not expired" }); } } @@ -489,25 +503,28 @@ export const authRouter = createTRPCRouter({ const conn = ConnectionFactory(); const res = await conn.execute({ sql: "SELECT * FROM User WHERE email = ?", - args: [email], + args: [email] }); if (res.rows.length === 0) { throw new TRPCError({ code: "NOT_FOUND", - message: "User not found", + message: "User not found" }); } // Create JWT token for email link (15min expiry) const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); - const token = await new SignJWT({ email, rememberMe: rememberMe ?? false }) + const token = await new SignJWT({ + email, + rememberMe: rememberMe ?? false + }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("15m") .sign(secret); // Send email - const domain = env.VITE_DOMAIN || env.NEXT_PUBLIC_DOMAIN; + const domain = env.VITE_DOMAIN || "https://freno.me"; const htmlContent = `