This commit is contained in:
alma 2025-05-02 18:05:28 +02:00
parent 3b81ad058a
commit a4412c081a
9 changed files with 93 additions and 187 deletions

View File

@ -15,13 +15,15 @@ const menuItems: Record<string, string> = {
dossiers: process.env.NEXT_PUBLIC_IFRAME_DRIVE_URL || '',
'the-message': process.env.NEXT_PUBLIC_IFRAME_THEMESSAGE_URL || '',
qg: process.env.NEXT_PUBLIC_IFRAME_MISSIONVIEW_URL || '',
// Keep any existing custom ones
board: "https://example.com/board",
chapter: "https://example.com/chapter",
flow: "https://example.com/flow",
design: "https://example.com/design",
// Use environment variables for these items too
board: process.env.NEXT_PUBLIC_IFRAME_BOARD_URL || '',
chapter: process.env.NEXT_PUBLIC_IFRAME_CHAPTER_URL || '',
flow: process.env.NEXT_PUBLIC_IFRAME_FLOW_URL || '',
design: process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || '',
artlab: process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || '',
// External URLs
gitlab: "https://gitlab.com",
missions: "https://example.com/missions"
missions: process.env.NEXT_PUBLIC_IFRAME_MISSIONSBOARD_URL || ''
}
// Using a different approach for metadata that doesn't directly access params.section

View File

@ -82,25 +82,13 @@ export const authOptions: NextAuthOptions = {
},
callbacks: {
async jwt({ token, account, profile, user }: any) {
// Debug input parameters to understand what's available
console.log('JWT callback - input parameters:', {
hasAccount: !!account,
hasProfile: !!profile,
hasUser: !!user,
hasToken: !!token,
tokenSub: token?.sub,
tokenAccessToken: token?.accessToken ? '[exists]' : undefined
});
// Initial sign in
if (account && account.access_token) {
console.log('JWT callback - NEW SIGN IN with access token detected');
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
// Process the raw profile data if available
if (user && user.raw_profile) {
console.log('JWT callback - Raw profile data found, extracting roles');
const rawProfile = user.raw_profile;
// Extract roles from all possible sources
@ -108,7 +96,6 @@ export const authOptions: NextAuthOptions = {
// Get roles from realm_access
if (rawProfile.realm_access && Array.isArray(rawProfile.realm_access.roles)) {
console.log('Found realm_access roles:', rawProfile.realm_access.roles);
roles = roles.concat(rawProfile.realm_access.roles);
}
@ -118,14 +105,12 @@ export const authOptions: NextAuthOptions = {
if (clientId &&
rawProfile.resource_access[clientId] &&
Array.isArray(rawProfile.resource_access[clientId].roles)) {
console.log('Found client-specific roles:', rawProfile.resource_access[clientId].roles);
roles = roles.concat(rawProfile.resource_access[clientId].roles);
}
// Also check resource_access roles under 'account'
if (rawProfile.resource_access.account &&
Array.isArray(rawProfile.resource_access.account.roles)) {
console.log('Found account roles:', rawProfile.resource_access.account.roles);
roles = roles.concat(rawProfile.resource_access.account.roles);
}
}
@ -138,18 +123,13 @@ export const authOptions: NextAuthOptions = {
// Always ensure user has basic user role
const finalRoles = [...new Set([...cleanedRoles, 'user'])];
// Add the application-specific roles for testing
finalRoles.push('admin', 'dataintelligence', 'coding', 'expression', 'mediation');
// Store roles in token
token.role = [...new Set(finalRoles)]; // Ensure uniqueness
console.log('JWT callback - Extracted roles:', token.role);
// Map Keycloak roles to application roles
token.role = mapToApplicationRoles(finalRoles);
} else if (user && user.role) {
console.log('JWT callback - Using roles from user object:', user.role);
token.role = Array.isArray(user.role) ? user.role : [user.role];
} else {
console.log('JWT callback - No profile data, setting default roles');
token.role = ['user', 'admin', 'dataintelligence', 'coding', 'expression', 'mediation'];
// Default roles if no profile data available
token.role = ['user'];
}
// Store user information
@ -159,30 +139,14 @@ export const authOptions: NextAuthOptions = {
token.last_name = user.last_name || '';
}
}
// Token exists but no roles, add default roles for testing
// Token exists but no roles, add default user role
else if (token && !token.role) {
console.log('JWT callback - Token exists but no roles, adding defaults');
// For testing purposes, add all roles
token.role = ['user', 'admin', 'dataintelligence', 'coding', 'expression', 'mediation'];
token.role = ['user'];
}
// Log the token roles
console.log('JWT token structure:', JSON.stringify({
sub: token.sub,
role: token.role,
username: token.username
}, null, 2));
console.log('JWT token roles:', token.role);
return token;
},
async session({ session, token }: any) {
console.log('Session callback - input parameters:', {
hasSession: !!session,
hasToken: !!token,
tokenRole: token?.role,
tokenSub: token?.sub
});
// Pass necessary info to the session
session.accessToken = token.accessToken;
if (session.user) {
@ -194,18 +158,13 @@ export const authOptions: NextAuthOptions = {
session.user.username = token.username || '';
session.user.first_name = token.first_name || '';
session.user.last_name = token.last_name || '';
console.log('Session callback - using token roles:', token.role);
} else {
// Fallback roles - ENSURE ALL REQUIRED ROLES ARE INCLUDED
session.user.role = ["user", "admin", "dataintelligence", "coding", "expression", "mediation"];
// Fallback roles
session.user.role = ["user"];
session.user.username = '';
session.user.first_name = '';
session.user.last_name = '';
console.log('Session callback - no token roles, using fallback');
}
// Log the session user roles
console.log('Session user roles:', session.user.role);
}
return session;
}
@ -225,7 +184,7 @@ export const authOptions: NextAuthOptions = {
},
},
},
debug: true, // Enable debug logs to help with troubleshooting
debug: process.env.NODE_ENV === 'development',
};
/**
@ -250,39 +209,21 @@ function mapToApplicationRoles(keycloakRoles: string[]): string[] {
// Add more mappings as needed
};
// Convert all keycloak roles to lowercase for case-insensitive matching
const lowerKeycloakRoles = keycloakRoles.map(role => role.toLowerCase());
// Map each role and flatten the result
let appRoles: string[] = ['user']; // Always include 'user' role
// Map roles based on the defined mappings
let applicationRoles: string[] = [];
// Check all Keycloak roles for matches in our mapping
for (const role of lowerKeycloakRoles) {
if (mappings[role]) {
applicationRoles = applicationRoles.concat(mappings[role]);
}
// Handle any role that contains certain keywords
if (role.includes('admin')) {
applicationRoles.push('admin', 'dataintelligence', 'coding', 'expression', 'mediation');
} else if (role.includes('developer') || role.includes('dev')) {
applicationRoles.push('coding', 'dataintelligence');
} else if (role.includes('design')) {
applicationRoles.push('expression');
} else if (role.includes('data')) {
applicationRoles.push('dataintelligence');
} else if (role.includes('mediat')) {
applicationRoles.push('mediation');
for (const role of keycloakRoles) {
const mappedRoles = mappings[role.toLowerCase()];
if (mappedRoles) {
appRoles = [...appRoles, ...mappedRoles];
}
}
// Ensure user always has basic access
applicationRoles.push('user');
// Return unique application roles
return [...new Set(applicationRoles)];
// Remove duplicates and return
return [...new Set(appRoles)];
}
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@ -10,14 +10,14 @@ export default async function ArtlabPage() {
redirect("/signin");
}
// Get the direct URL from environment variable
const iframeUrl = process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || '';
// Get the design URL from environment variable - intentionally shares URL with design section
const designIframeUrl = process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || '';
return (
<main className="w-full h-screen bg-black">
<div className="w-full h-full px-4 pt-12 pb-4">
<ResponsiveIframe
src={iframeUrl}
src={designIframeUrl}
allowFullScreen={true}
/>
</div>

View File

@ -27,7 +27,6 @@ const SERVICE_URLS: Record<string, string> = {
'the-message': process.env.NEXT_PUBLIC_IFRAME_THEMESSAGE_URL || '',
'qg': process.env.NEXT_PUBLIC_IFRAME_MISSIONVIEW_URL || '',
'design': process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || '',
'artlab': process.env.NEXT_PUBLIC_IFRAME_DESIGN_URL || ''
};
export default function ResponsiveIframe({

View File

@ -1,37 +0,0 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
export function Todo() {
const todos = [
{ text: "send e-mails", done: false },
{ text: "do the visuals", done: false },
{ text: "write the contract", done: false },
];
return (
<Card className='transition-transform duration-500 ease-in-out transform hover:scale-105'>
<CardHeader>
<CardTitle>Todo</CardTitle>
</CardHeader>
<CardContent>
<div className='space-y-4'>
{todos.map((todo, i) => (
<div key={i} className='flex items-center space-x-2'>
<Checkbox id={`todo-${i}`} />
<label
htmlFor={`todo-${i}`}
className='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
>
{todo.text}
</label>
</div>
))}
<Button className='w-full mt-4' variant='secondary'>
Add Todo
</Button>
</div>
</CardContent>
</Card>
);
}

View File

@ -473,7 +473,17 @@ export const useEmailState = () => {
const newFlaggedStatus = !email.flags.flagged;
logEmailOp('TOGGLE_STAR', `Setting starred status to ${newFlaggedStatus} for email ${emailId}`);
// TODO: Implement optimistic update
// Implement optimistic update
dispatch({
type: 'UPDATE_EMAIL_FLAGS',
payload: {
emailId,
flags: {
flagged: newFlaggedStatus
},
accountId: email.accountId
}
});
// Make API call
const response = await fetch(`/api/courrier/${emailId}/flag`, {

View File

@ -52,6 +52,7 @@ export type EmailAction =
| { type: 'UPDATE_UNREAD_COUNT', payload: { accountId: string, folder: string, count: number } }
| { type: 'SET_UNREAD_COUNTS', payload: Record<string, Record<string, number>> }
| { type: 'TOGGLE_SHOW_FOLDERS', payload: boolean }
| { type: 'UPDATE_EMAIL_FLAGS', payload: { emailId: string, flags: { flagged: boolean }, accountId?: string } }
| { type: 'MARK_EMAIL_AS_READ', payload: { emailId: string, isRead: boolean, accountId?: string } };
// Initial state
@ -420,6 +421,30 @@ export function emailReducer(state: EmailState, action: EmailAction): EmailState
showFolders: action.payload
};
case 'UPDATE_EMAIL_FLAGS': {
const { emailId, flags, accountId } = action.payload;
// Update emails list
const updatedEmails = state.emails.map(email =>
(email.id === emailId && (!accountId || email.accountId === accountId))
? { ...email, flags: { ...email.flags, ...flags } }
: email
);
// Update selected email if it matches
const updatedSelectedEmail = state.selectedEmail &&
state.selectedEmail.id === emailId &&
(!accountId || state.selectedEmail.accountId === accountId)
? { ...state.selectedEmail, flags: { ...state.selectedEmail.flags, ...flags } }
: state.selectedEmail;
return {
...state,
emails: updatedEmails,
selectedEmail: updatedSelectedEmail
};
}
case 'MARK_EMAIL_AS_READ': {
const { emailId, isRead, accountId } = action.payload;

View File

@ -12,6 +12,33 @@ const nextConfig = {
}
return config;
},
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
experimental: {
webpackBuildWorker: true,
parallelServerBuildTraces: true,
parallelServerCompiles: true,
},
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: "frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net"
}
]
}
]
}
};
module.exports = nextConfig;

View File

@ -1,61 +0,0 @@
let userConfig;
try {
userConfig = await import("./v0-user-next.config");
} catch (e) {
// ignore error
}
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
experimental: {
webpackBuildWorker: true,
parallelServerBuildTraces: true,
parallelServerCompiles: true,
},
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: "frame-ancestors 'self' https://espace.slm-lab.net https://connect.slm-lab.net"
}
]
}
]
}
};
mergeConfig(nextConfig, userConfig);
function mergeConfig(nextConfig, userConfig) {
if (!userConfig) {
return;
}
for (const key in userConfig) {
if (
typeof nextConfig[key] === "object" &&
!Array.isArray(nextConfig[key])
) {
nextConfig[key] = {
...nextConfig[key],
...userConfig[key],
};
} else {
nextConfig[key] = userConfig[key];
}
}
}
export default nextConfig;