From f3c90ba29022bfc5e46179daa9c72a99d3a073d9 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Thu, 7 May 2026 11:11:25 +0800 Subject: [PATCH 1/4] chore: bump version to 0.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f305d8..3639fb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bun-react-template", - "version": "0.1.3", + "version": "0.1.4", "private": true, "type": "module", "scripts": { -- 2.49.1 From f469faf740d7ee3d3a7b0ffaa2bd7364463c7803 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Thu, 7 May 2026 11:12:30 +0800 Subject: [PATCH 2/4] fix: add Secure flag to session cookies in production --- src/app.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app.ts b/src/app.ts index 91e2f03..4a4d98d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,6 +11,9 @@ import { getMinioDownloadUrl, uploadBugImage } from './lib/minio' import { addConnection, broadcastToAdmins, getOnlineUserIds, removeConnection } from './lib/presence' import { parseSchema } from './lib/schema-parser' +const isProduction = process.env.NODE_ENV === 'production' +const cookieFlags = isProduction ? '; Secure' : '' + function getPublicOrigin(request: Request): string { if (process.env.BUN_PUBLIC_BASE_URL) return process.env.BUN_PUBLIC_BASE_URL.replace(/\/$/, '') const url = new URL(request.url) @@ -127,7 +130,7 @@ export function createApp() { }) const headers = new Headers() headers.set('Location', `https://accounts.google.com/o/oauth2/v2/auth?${params}`) - headers.set('Set-Cookie', `oauth_state=${state}; Path=/; HttpOnly; SameSite=Lax; Max-Age=600`) + headers.set('Set-Cookie', `oauth_state=${state}; Path=/; HttpOnly; SameSite=Lax; Max-Age=600${cookieFlags}`) return new Response(null, { status: 302, headers }) }, { detail: { @@ -212,8 +215,8 @@ export function createApp() { const redirectPath = user.role === 'DEVELOPER' ? '/dev' : user.role === 'USER' ? '/profile' : '/dashboard' const headers = new Headers() headers.append('Location', redirectPath) - headers.append('Set-Cookie', `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`) - headers.append('Set-Cookie', 'oauth_state=; Path=/; HttpOnly; Max-Age=0') + headers.append('Set-Cookie', `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${cookieFlags}`) + headers.append('Set-Cookie', `oauth_state=; Path=/; HttpOnly; Max-Age=0${cookieFlags}`) return new Response(null, { status: 302, headers }) }, { detail: { @@ -241,7 +244,7 @@ export function createApp() { const token = crypto.randomUUID() const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours await prisma.session.create({ data: { token, userId: user.id, expiresAt } }) - set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` + set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${cookieFlags}` await createSystemLog(user.id, 'LOGIN', 'Logged in successfully') return { user: { id: user.id, name: user.name, email: user.email, role: user.role, image: user.image } } }, { @@ -266,7 +269,7 @@ export function createApp() { await prisma.session.deleteMany({ where: { token } }) } } - set.headers['set-cookie'] = 'session=; Path=/; HttpOnly; Max-Age=0' + set.headers['set-cookie'] = `session=; Path=/; HttpOnly; Max-Age=0${cookieFlags}` return { ok: true } }, { detail: { -- 2.49.1 From 032386a549f335749000a2b062c16223a6313ae9 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Thu, 7 May 2026 12:08:34 +0800 Subject: [PATCH 3/4] feat: redesign login and splash screen with playful visual lift --- src/frontend/routes/index.tsx | 82 +++++++++++----- src/frontend/routes/login.tsx | 172 ++++++++++++++++++++++------------ 2 files changed, 174 insertions(+), 80 deletions(-) diff --git a/src/frontend/routes/index.tsx b/src/frontend/routes/index.tsx index a325f74..a1c3740 100644 --- a/src/frontend/routes/index.tsx +++ b/src/frontend/routes/index.tsx @@ -1,7 +1,6 @@ -import { Button, Container, Group, Stack, Text, Title } from '@mantine/core' +import { Button, Box, Center, Stack, Text, Title } from '@mantine/core' import { Link, createFileRoute } from '@tanstack/react-router' -import { SiBun } from 'react-icons/si' -import { TbBrandReact, TbLogin, TbRocket } from 'react-icons/tb' +import { TbLogin } from 'react-icons/tb' export const Route = createFileRoute('/')({ component: HomePage, @@ -9,28 +8,67 @@ export const Route = createFileRoute('/')({ function HomePage() { return ( - - - - - - + + {/* background blobs */} + + + - Bun + Elysia + Vite + React +
+ + logo - - Full-stack starter template with Mantine UI, TanStack Router, and session-based auth. - + + + Monitoring System + + + Pantau semua aplikasi dalam satu tempat, real-time. + + - - - - - - + +
+
) } diff --git a/src/frontend/routes/login.tsx b/src/frontend/routes/login.tsx index ff41ee9..e3d595b 100644 --- a/src/frontend/routes/login.tsx +++ b/src/frontend/routes/login.tsx @@ -1,10 +1,10 @@ import { useLogin } from '@/frontend/hooks/useAuth' import { Alert, + Box, Button, Center, Divider, - Paper, PasswordInput, Stack, Text, @@ -38,6 +38,14 @@ export const Route = createFileRoute('/login')({ component: LoginPage, }) +const OAUTH_ERRORS: Record = { + google_denied: 'Login dengan Google dibatalkan.', + invalid_state: 'Sesi OAuth tidak valid, silakan coba lagi.', + token_failed: 'Gagal menukar token Google, silakan coba lagi.', + userinfo_failed: 'Gagal mengambil info akun Google, silakan coba lagi.', + account_disabled: 'Akun Anda telah dinonaktifkan. Hubungi admin untuk informasi lebih lanjut.', +} + function LoginPage() { const login = useLogin() const { error: searchError } = Route.useSearch() @@ -49,69 +57,117 @@ function LoginPage() { login.mutate({ email, password }) } + const errorMessage = login.isError + ? login.error.message + : searchError + ? (OAUTH_ERRORS[searchError] ?? 'Login dengan Google gagal, silakan coba lagi.') + : null + return ( -
- -
- - - Login - + + {/* background blobs */} + + + - {(login.isError || searchError) && ( - } color="red" variant="light"> - {login.isError ? login.error.message : ( - { - google_denied: 'Login dengan Google dibatalkan.', - invalid_state: 'Sesi OAuth tidak valid, silakan coba lagi.', - token_failed: 'Gagal menukar token Google, silakan coba lagi.', - userinfo_failed: 'Gagal mengambil info akun Google, silakan coba lagi.', - account_disabled: 'Akun Anda telah dinonaktifkan. Hubungi admin untuk informasi lebih lanjut.', - }[searchError ?? ''] ?? 'Login dengan Google gagal, silakan coba lagi.' - )} - - )} +
+ + + + {/* header */} + + logo + + Monitoring System + + + Masuk untuk melanjutkan + + - } - value={email} - onChange={(e) => setEmail(e.currentTarget.value)} - required - /> + {errorMessage && ( + } color="red" variant="light"> + {errorMessage} + + )} - } - value={password} - onChange={(e) => setPassword(e.currentTarget.value)} - required - /> + } + value={email} + onChange={(e) => setEmail(e.currentTarget.value)} + required + /> - + } + value={password} + onChange={(e) => setPassword(e.currentTarget.value)} + required + /> - + - - - - -
+ + + +
+ + +
+
) } -- 2.49.1 From f926ab2701a5d39faa2a6e8ece08379ffc974b6b Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Thu, 7 May 2026 12:21:08 +0800 Subject: [PATCH 4/4] feat: add colored top border to stats cards --- src/frontend/components/StatsCard.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/frontend/components/StatsCard.tsx b/src/frontend/components/StatsCard.tsx index 8525456..09e9ffd 100644 --- a/src/frontend/components/StatsCard.tsx +++ b/src/frontend/components/StatsCard.tsx @@ -14,18 +14,21 @@ interface StatsCardProps { } export function StatsCard({ title, value, description, icon: Icon, color, trend }: StatsCardProps) { + const accentColor = `var(--mantine-color-${color ?? 'brand-blue'}-5)` + return ( ({ + styles={{ root: { backgroundColor: 'var(--mantine-color-body)', borderColor: 'rgba(128,128,128,0.1)', + borderTop: `3px solid ${accentColor}`, }, - })} + }} >