feat(auth): header login/logout and /me badge using ky + react-query
This commit is contained in:
@ -43,6 +43,11 @@ steps:
|
||||
build_args:
|
||||
- VITE_API_BASE_URL=https://rubble.se/hemhub/api/
|
||||
- VITE_BASE_PATH=/hemhub/app/
|
||||
- VITE_OIDC_AUTHORITY=https://rubble.se/auth/realms/hemhub
|
||||
- VITE_OIDC_CLIENT_ID=hemhub-public
|
||||
- VITE_OIDC_REDIRECT_URI=https://rubble.se/hemhub/app/auth/callback
|
||||
- VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://rubble.se/hemhub/app/
|
||||
- VITE_OIDC_SILENT_REDIRECT_URI=https://rubble.se/hemhub/app/auth/silent-renew
|
||||
|
||||
# Taggar (latest + kort SHA)
|
||||
tags:
|
||||
|
||||
43
Dockerfile
43
Dockerfile
@ -1,48 +1,41 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
############################
|
||||
# Build stage
|
||||
############################
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
# pnpm via Corepack
|
||||
ENV PNPM_HOME="/root/.local/share/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && corepack prepare pnpm@9.12.0 --activate
|
||||
|
||||
# Installera beroenden
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# App-källor
|
||||
COPY . .
|
||||
|
||||
# --- Build args (styr Vite) ---
|
||||
# Backend-url (kan sättas i Drone secrets)
|
||||
ARG VITE_API_BASE_URL=https://rubble.se/hemhub/api
|
||||
# Base path för proxy under /hemhub/
|
||||
ARG VITE_BASE_PATH=/hemhub/app
|
||||
ENV VITE_API_BASE_URL=https://rubble.se/hemhub/api
|
||||
ENV VITE_BASE_PATH=/hemhub/app
|
||||
# --- Build args (med BRA defaults) ---
|
||||
ARG VITE_API_BASE_URL=http://localhost:8080
|
||||
ARG VITE_BASE_PATH=/
|
||||
|
||||
# Gör ARG:arna synliga för Vite config (vite.config.ts läser process.env.VITE_*)
|
||||
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
||||
ENV VITE_BASE_PATH=$VITE_BASE_PATH
|
||||
|
||||
ARG VITE_OIDC_AUTHORITY=
|
||||
ARG VITE_OIDC_CLIENT_ID=
|
||||
ARG VITE_OIDC_REDIRECT_URI=
|
||||
ARG VITE_OIDC_POST_LOGOUT_REDIRECT_URI=
|
||||
ARG VITE_OIDC_SILENT_REDIRECT_URI=
|
||||
ENV VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY \
|
||||
VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID \
|
||||
VITE_OIDC_REDIRECT_URI=$VITE_OIDC_REDIRECT_URI \
|
||||
VITE_OIDC_POST_LOGOUT_REDIRECT_URI=$VITE_OIDC_POST_LOGOUT_REDIRECT_URI \
|
||||
VITE_OIDC_SILENT_REDIRECT_URI=$VITE_OIDC_SILENT_REDIRECT_URI
|
||||
|
||||
# Bygg (Vite läser env vid build)
|
||||
RUN pnpm build
|
||||
|
||||
|
||||
############################
|
||||
# Runtime stage (Nginx)
|
||||
############################
|
||||
FROM nginx:1.27-alpine AS runtime
|
||||
|
||||
# Lägg in Nginx-konfig (SPA fallback)
|
||||
COPY ./.docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Statiska filer från builden
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Hälsokoll (valfritt)
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -qO- http://127.0.0.1/ || exit 1
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@ -5,8 +5,14 @@ import { DashboardPage } from '@/pages/DashboardPage'
|
||||
import { HouseholdBoardPage } from '@/pages/HouseholdBoardPage'
|
||||
import { ProjectBoardPage } from '@/pages/ProjectBoardPage'
|
||||
import { DueTomorrowPage } from '@/pages/DueTomorrowPage'
|
||||
import AuthCallbackPage from '@/pages/AuthCallbackPage'
|
||||
import SilentRenewPage from '@/pages/SilentRenewPage'
|
||||
import LogoutPage from '@/pages/LogoutPage'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{ path: '/auth/callback', element: <AuthCallbackPage /> },
|
||||
{ path: '/auth/silent-renew', element: <SilentRenewPage /> },
|
||||
{ path: '/logout', element: <LogoutPage /> },
|
||||
{
|
||||
path: '/',
|
||||
element: <RootLayout />,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
// src/components/layout/RootLayout.tsx
|
||||
import { Outlet, NavLink } from 'react-router-dom'
|
||||
import { MeBadge } from '../../features/me/MeBadge'
|
||||
import { useAuth } from 'auth/AuthProvider'
|
||||
|
||||
export function RootLayout() {
|
||||
const { isAuthenticated, signIn } = useAuth()
|
||||
return (
|
||||
<div className="min-h-dvh bg-zinc-50 text-zinc-900">
|
||||
<header className="sticky top-0 border-b bg-white/70 backdrop-blur supports-[backdrop-filter]:bg-white/40">
|
||||
@ -11,6 +14,12 @@ export function RootLayout() {
|
||||
<NavLink to="/" className={({isActive})=>isActive?'font-semibold underline':'opacity-80 hover:opacity-100'}>Dashboard</NavLink>
|
||||
<NavLink to="/tasks/due-tomorrow" className={({isActive})=>isActive?'font-semibold underline':'opacity-80 hover:opacity-100'}>Due tomorrow</NavLink>
|
||||
</nav>
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
<MeBadge />
|
||||
{isAuthenticated
|
||||
? <NavLink to="/logout" className="text-sm underline">Logga ut</NavLink>
|
||||
: <button onClick={()=>signIn(window.location.pathname+window.location.search)} className="text-sm underline">Logga in</button>}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="mx-auto max-w-6xl px-4 py-6">
|
||||
|
||||
9
src/features/me/MeBadge.tsx
Normal file
9
src/features/me/MeBadge.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchMe } from './api'
|
||||
|
||||
export function MeBadge() {
|
||||
const { data, isLoading, isError } = useQuery({ queryKey: ['me'], queryFn: fetchMe, retry: 0 })
|
||||
if (isLoading) return <span className="opacity-60">…</span>
|
||||
if (isError) return <span className="opacity-60">ej inloggad</span>
|
||||
return <span className="opacity-80 text-sm">{data?.name || data?.preferred_username || 'me'}</span>
|
||||
}
|
||||
3
src/features/me/api.ts
Normal file
3
src/features/me/api.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { api } from '@/lib/http'
|
||||
export type Me = { name?: string; preferred_username?: string; email?: string }
|
||||
export const fetchMe = () => api.get('me').json<Me>()
|
||||
23
src/lib/http.ts
Normal file
23
src/lib/http.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// src/lib/http.ts
|
||||
import ky from 'ky'
|
||||
|
||||
export const api = ky.create({
|
||||
// pekar på ditt API, t.ex. https://rubble.se/hemhub/api/ i prod (sätts via VITE_API_BASE_URL)
|
||||
prefixUrl: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080/',
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
async (req) => {
|
||||
const token = sessionStorage.getItem('access_token')
|
||||
if (token) req.headers.set('Authorization', `Bearer ${token}`)
|
||||
},
|
||||
],
|
||||
afterResponse: [
|
||||
async (_req, _opts, res) => {
|
||||
if (res.status === 401) {
|
||||
// I Iteration 1, mjuk hantering — vi låter RequireAuth sköta redirecten
|
||||
console.warn('401 from API – probably not logged in yet')
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
4
src/lib/queryKeys.ts
Normal file
4
src/lib/queryKeys.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// src/lib/queryKeys.ts
|
||||
export const qk = {
|
||||
me: ['me'] as const,
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
|
||||
Reference in New Issue
Block a user