fix: resolve frontend lint errors and cleanup types
CI Pipeline / Backend Build & Test (pull_request) Successful in 53s
CI Pipeline / Frontend Lint, Test & Build (pull_request) Successful in 36s
CI Pipeline / Infrastructure Validation (pull_request) Successful in 4s

This commit is contained in:
WorkClub Automation
2026-03-18 09:15:02 +01:00
parent 821459966c
commit d30895c94a
5 changed files with 33 additions and 19 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ export default function ProtectedLayout({
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { data } = useSession(); const { data } = useSession();
const isAdmin = (data?.user as any)?.isAdmin; const isAdmin = data?.user?.isAdmin;
return ( return (
<AuthGuard> <AuthGuard>
@@ -7,7 +7,6 @@ import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useSession } from 'next-auth/react';
export default function ShiftDetailPage({ params }: { params: Promise<{ id: string }> }) { export default function ShiftDetailPage({ params }: { params: Promise<{ id: string }> }) {
const resolvedParams = use(params); const resolvedParams = use(params);
@@ -15,7 +14,6 @@ export default function ShiftDetailPage({ params }: { params: Promise<{ id: stri
const signUpMutation = useSignUpShift(); const signUpMutation = useSignUpShift();
const cancelMutation = useCancelSignUp(); const cancelMutation = useCancelSignUp();
const router = useRouter(); const router = useRouter();
const { data: session } = useSession();
if (isLoading) return <div>Loading shift...</div>; if (isLoading) return <div>Loading shift...</div>;
if (!shift) return <div>Shift not found</div>; if (!shift) return <div>Shift not found</div>;
+7 -7
View File
@@ -47,14 +47,14 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
async jwt({ token, account }) { async jwt({ token, account }) {
if (account && account.access_token) { if (account && account.access_token) {
// Add clubs claim from Keycloak access token // Add clubs claim from Keycloak access token
token.clubs = (account as any).clubs as Record<string, string> || {} token.clubs = (account as { clubs?: Record<string, string> }).clubs || {}
token.accessToken = account.access_token as string token.accessToken = account.access_token
try { try {
const payload = JSON.parse(Buffer.from((token.accessToken as string).split('.')[1], 'base64').toString()); const payload = JSON.parse(Buffer.from((token.accessToken as string).split('.')[1], 'base64').toString());
const roles = payload.realm_access?.roles || []; const roles = (payload.realm_access?.roles as string[]) || [];
token.isAdmin = roles.includes('admin'); token.isAdmin = roles.includes('admin');
} catch (e) { } catch {
token.isAdmin = false; token.isAdmin = false;
} }
} }
@@ -63,10 +63,10 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
async session({ session, token }) { async session({ session, token }) {
// Expose clubs to client // Expose clubs to client
if (session.user) { if (session.user) {
session.user.clubs = (token as any).clubs as Record<string, string> | undefined session.user.clubs = token.clubs as Record<string, string> | undefined
session.user.isAdmin = (token as any).isAdmin as boolean | undefined session.user.isAdmin = token.isAdmin as boolean | undefined
} }
session.accessToken = (token as any).accessToken as string | undefined session.accessToken = token.accessToken as string | undefined
return session return session
} }
} }
@@ -17,10 +17,30 @@ export function ClubManagement() {
const [isCreating, setIsCreating] = useState(false); const [isCreating, setIsCreating] = useState(false);
const [newClub, setNewClub] = useState({ name: '', sportType: 'Tennis', description: '' }); const [newClub, setNewClub] = useState({ name: '', sportType: 'Tennis', description: '' });
useEffect(() => {
const fetchClubsLocally = async () => {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/admin/clubs`, {
headers: { Authorization: `Bearer ${session?.accessToken}` },
});
if (res.ok) {
const data = await res.json();
setClubs(data);
}
} catch (error) {
console.error('Failed to fetch clubs', error);
} finally {
setLoading(false);
}
};
if (session) fetchClubsLocally();
}, [session]);
const fetchClubs = async () => { const fetchClubs = async () => {
try { try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/admin/clubs`, { const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/admin/clubs`, {
headers: { Authorization: `Bearer ${(session as any)?.accessToken}` }, headers: { Authorization: `Bearer ${session?.accessToken}` },
}); });
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
@@ -33,10 +53,6 @@ export function ClubManagement() {
} }
}; };
useEffect(() => {
if (session) fetchClubs();
}, [session]);
const handleCreate = async (e: React.FormEvent) => { const handleCreate = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
try { try {
@@ -44,7 +60,7 @@ export function ClubManagement() {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${(session as any)?.accessToken}`, Authorization: `Bearer ${session?.accessToken}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
name: newClub.name, name: newClub.name,
@@ -67,7 +83,7 @@ export function ClubManagement() {
try { try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/admin/clubs/${id}`, { const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/admin/clubs/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { Authorization: `Bearer ${(session as any)?.accessToken}` }, headers: { Authorization: `Bearer ${session?.accessToken}` },
}); });
if (res.ok) { if (res.ok) {
fetchClubs(); fetchClubs();
+2 -2
View File
@@ -18,7 +18,7 @@ export function AuthGuard({ children }: { children: ReactNode }) {
useEffect(() => { useEffect(() => {
if (status === 'authenticated') { if (status === 'authenticated') {
const isAdmin = (data?.user as any)?.isAdmin; const isAdmin = data?.user?.isAdmin;
// Admin routing // Admin routing
if (isAdmin) { if (isAdmin) {
@@ -59,7 +59,7 @@ export function AuthGuard({ children }: { children: ReactNode }) {
); );
} }
const isAdmin = (data?.user as any)?.isAdmin; const isAdmin = data?.user?.isAdmin;
if (clubs.length === 0 && status === 'authenticated' && !isAdmin) { if (clubs.length === 0 && status === 'authenticated' && !isAdmin) {
const handleSwitchAccount = () => { const handleSwitchAccount = () => {
const keycloakLogoutUrl = `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER || 'http://localhost:8080/realms/workclub'}/protocol/openid-connect/logout?redirect_uri=${encodeURIComponent(window.location.origin + '/login')}`; const keycloakLogoutUrl = `${process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER || 'http://localhost:8080/realms/workclub'}/protocol/openid-connect/logout?redirect_uri=${encodeURIComponent(window.location.origin + '/login')}`;