feat: implement platform management system with authentication and dashboard

- Add PlatformUser model with roles (SUPER_ADMIN, ADMIN, SUPPORT)
- Implement platform authentication with NextAuth
- Create platform dashboard showing companies, users, and sessions
- Add platform API endpoints for company management
- Update landing page with SaaS design
- Include test improvements and accessibility updates
This commit is contained in:
2025-06-28 12:41:50 +02:00
parent aa0e9d5ebc
commit 60d1b72aba
18 changed files with 1190 additions and 53 deletions

View File

@ -25,7 +25,7 @@ const mockExistingUsers = [
},
{
id: "user-2",
email: "admin@example.com",
email: "admin@example.com",
role: "ADMIN",
companyId: "test-company-id",
},
@ -332,7 +332,7 @@ describe("User Invitation Integration Tests", () => {
describe("Email Validation Edge Cases", () => {
it("should handle very long email addresses", async () => {
const longEmail = "a".repeat(250) + "@example.com";
const { req, res } = createMocks({
method: "POST",
body: {
@ -354,7 +354,7 @@ describe("User Invitation Integration Tests", () => {
it("should handle special characters in email", async () => {
const specialEmail = "test+tag@example-domain.co.uk";
const { req, res } = createMocks({
method: "POST",
body: {
@ -424,7 +424,7 @@ describe("User Invitation Integration Tests", () => {
it("should handle multiple rapid invitations", async () => {
const emails = [
"user1@example.com",
"user2@example.com",
"user2@example.com",
"user3@example.com",
"user4@example.com",
"user5@example.com",

View File

@ -56,7 +56,7 @@ describe("Accessibility Tests", () => {
);
await screen.findByText("User Management");
// Basic accessibility check - most critical violations would be caught here
const results = await axe(container);
expect(results.violations.length).toBeLessThan(5); // Allow minor violations
@ -189,11 +189,11 @@ describe("Accessibility Tests", () => {
const emailInput = screen.getByLabelText("Email");
const submitButton = screen.getByRole("button", { name: /invite user/i });
// Elements should be focusable
emailInput.focus();
expect(emailInput).toHaveFocus();
submitButton.focus();
expect(submitButton).toHaveFocus();
});

View File

@ -99,7 +99,7 @@ describe("Format Enums Utility", () => {
it("should be an alias for formatEnumValue", () => {
const testValues = [
"SALARY_COMPENSATION",
"SCHEDULE_HOURS",
"SCHEDULE_HOURS",
"UNKNOWN_ENUM",
null,
undefined,
@ -129,7 +129,7 @@ describe("Format Enums Utility", () => {
it("should handle very long enum values", () => {
const longEnum = "A".repeat(100) + "_" + "B".repeat(100);
const result = formatEnumValue(longEnum);
expect(result).toBeTruthy();
expect(result?.length).toBeGreaterThan(200);
expect(result?.includes(" ")).toBeTruthy();
@ -150,16 +150,16 @@ describe("Format Enums Utility", () => {
it("should be performant with many calls", () => {
const testEnum = "SALARY_COMPENSATION";
const iterations = 1000;
const startTime = performance.now();
for (let i = 0; i < iterations; i++) {
formatEnumValue(testEnum);
}
const endTime = performance.now();
const duration = endTime - startTime;
// Should complete 1000 calls in reasonable time (less than 100ms)
expect(duration).toBeLessThan(100);
});
@ -208,7 +208,7 @@ describe("Format Enums Utility", () => {
it("should provide readable text for badges and labels", () => {
const badgeValues = [
"ADMIN",
"USER",
"USER",
"AUDITOR",
"UNRECOGNIZED_OTHER",
];
@ -249,7 +249,7 @@ describe("Format Enums Utility", () => {
// Future enum values should still be formatted reasonably
const futureEnums = [
"REMOTE_WORK_POLICY",
"SUSTAINABILITY_INITIATIVES",
"SUSTAINABILITY_INITIATIVES",
"DIVERSITY_INCLUSION",
"MENTAL_HEALTH_SUPPORT",
];

View File

@ -52,7 +52,7 @@ describe("Keyboard Navigation Tests", () => {
roleSelect.focus();
expect(roleSelect).toBeInTheDocument();
submitButton.focus();
expect(document.activeElement).toBe(submitButton);
});
@ -84,7 +84,7 @@ describe("Keyboard Navigation Tests", () => {
// Submit with Enter key
fireEvent.keyDown(submitButton, { key: "Enter" });
// Form should be submitted (fetch called for initial load + submission)
expect(global.fetch).toHaveBeenCalledTimes(2);
});
@ -117,7 +117,7 @@ describe("Keyboard Navigation Tests", () => {
// Activate with Space key
submitButton.focus();
fireEvent.keyDown(submitButton, { key: " " });
// Should trigger form submission (fetch called for initial load + submission)
expect(global.fetch).toHaveBeenCalledTimes(3);
});
@ -153,7 +153,7 @@ describe("Keyboard Navigation Tests", () => {
// Press Escape
fireEvent.keyDown(emailInput, { key: "Escape" });
// Field should not be cleared by Escape (browser default behavior)
// But it should not cause any errors
expect(emailInput.value).toBe("test@example.com");
@ -173,7 +173,7 @@ describe("Keyboard Navigation Tests", () => {
// Arrow keys should work (implementation depends on Select component)
fireEvent.keyDown(roleSelect, { key: "ArrowDown" });
fireEvent.keyDown(roleSelect, { key: "ArrowUp" });
// Should not throw errors
expect(roleSelect).toBeInTheDocument();
});
@ -292,7 +292,7 @@ describe("Keyboard Navigation Tests", () => {
);
const chart = screen.getByRole("img", { name: /test chart/i });
// Chart should be focusable
chart.focus();
expect(chart).toHaveFocus();
@ -311,7 +311,7 @@ describe("Keyboard Navigation Tests", () => {
);
const chart = screen.getByRole("img", { name: /test chart/i });
chart.focus();
// Test keyboard interactions
@ -395,7 +395,7 @@ describe("Keyboard Navigation Tests", () => {
// Should handle focus on disabled elements gracefully
submitButton.focus();
fireEvent.keyDown(submitButton, { key: "Enter" });
// Should not cause errors
});
@ -515,7 +515,7 @@ describe("Keyboard Navigation Tests", () => {
await screen.findByText("User Management");
const emailInput = screen.getByLabelText("Email");
// Focus should still work in high contrast mode
emailInput.focus();
expect(emailInput).toHaveFocus();

View File

@ -263,7 +263,7 @@ describe("UserManagementPage", () => {
await waitFor(() => {
const emailInput = screen.getByLabelText("Email") as HTMLInputElement;
fireEvent.change(emailInput, { target: { value: "invalid-email" } });
fireEvent.blur(emailInput);