1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 13:49:36 +01:00

feat(frontend): TypeScript-Monorepo mit Terminal-UI für Effigenix ERP

Monorepo-Setup (pnpm workspaces) mit vier shared Packages und einer TUI-App:

Shared Packages:
- @effigenix/types: TypeScript-DTOs (UserDTO, RoleDTO, AuthDTO, Enums)
- @effigenix/config: API-Konfiguration und Shared Constants
- @effigenix/validation: Zod-Schemas für Username, E-Mail und Passwort
- @effigenix/api-client: axios-Client mit JWT-Handling (proaktiver + reaktiver
  Token-Refresh), AuthInterceptor, ErrorInterceptor, Resources für auth/users/roles

TUI (apps/cli, Ink 5 / React):
- Authentication: Login/Logout, Session-Restore beim Start, JWT-Refresh
- User Management: Liste, Anlage (Zod-Inline-Validation), Detailansicht,
  Passwort ändern, Sperren/Entsperren mit ConfirmDialog
- Role Management: Liste, Detailansicht, Zuweisen/Entfernen per RoleSelectList (↑↓)
- UX: SuccessDisplay (Auto-Dismiss 3 s), ConfirmDialog (J/N),
  FormInput mit Inline-Fehlern, StatusBar mit API-URL
- Layout: Fullscreen-Modus (alternate screen buffer), Header mit eingeloggtem User
- Tests: vitest + ink-testing-library (15 Tests)
This commit is contained in:
Sebastian Frick 2026-02-18 12:23:11 +01:00
parent 87123df2e4
commit bbe9e87c33
65 changed files with 6955 additions and 1 deletions

View file

@ -0,0 +1,37 @@
{
"name": "@effigenix/validation",
"version": "0.1.0",
"description": "Zod validation schemas for Effigenix ERP",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"dependencies": {
"@effigenix/config": "workspace:*",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.0",
"tsup": "^8.0.1",
"typescript": "^5.3.3"
},
"tsup": {
"entry": ["src/index.ts"],
"format": ["esm"],
"dts": true,
"clean": true,
"sourcemap": true,
"splitting": false
}
}

View file

@ -0,0 +1,3 @@
export * from './validators.js';
export * from './schemas/auth.js';
export * from './schemas/user.js';

View file

@ -0,0 +1,14 @@
import { z } from 'zod';
import { usernameSchema } from '../validators.js';
export const loginRequestSchema = z.object({
username: usernameSchema,
password: z.string().min(1, 'Passwort ist erforderlich'),
});
export const refreshTokenRequestSchema = z.object({
refreshToken: z.string().min(1, 'Refresh Token ist erforderlich'),
});
export type LoginRequestInput = z.input<typeof loginRequestSchema>;
export type LoginRequestOutput = z.output<typeof loginRequestSchema>;

View file

@ -0,0 +1,37 @@
import { z } from 'zod';
import { emailSchema, passwordSchema, usernameSchema, uuidSchema } from '../validators.js';
export const createUserRequestSchema = z.object({
username: usernameSchema,
email: emailSchema,
password: passwordSchema,
roleIds: z.array(uuidSchema).min(1, 'Mindestens eine Rolle muss zugewiesen werden'),
branchId: uuidSchema.optional(),
});
export const updateUserRequestSchema = z.object({
email: emailSchema.optional(),
branchId: uuidSchema.optional(),
});
export const changePasswordRequestSchema = z
.object({
currentPassword: z.string().min(1, 'Aktuelles Passwort ist erforderlich'),
newPassword: passwordSchema,
confirmPassword: z.string().min(1, 'Passwortbestätigung ist erforderlich'),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: 'Passwörter stimmen nicht überein',
path: ['confirmPassword'],
});
export const assignRoleRequestSchema = z.object({
roleId: uuidSchema,
});
export type CreateUserRequestInput = z.input<typeof createUserRequestSchema>;
export type CreateUserRequestOutput = z.output<typeof createUserRequestSchema>;
export type UpdateUserRequestInput = z.input<typeof updateUserRequestSchema>;
export type UpdateUserRequestOutput = z.output<typeof updateUserRequestSchema>;
export type ChangePasswordRequestInput = z.input<typeof changePasswordRequestSchema>;
export type AssignRoleRequestInput = z.input<typeof assignRoleRequestSchema>;

View file

@ -0,0 +1,28 @@
import { z } from 'zod';
import { PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH } from '@effigenix/config';
/**
* Reusable field validators
*/
export const emailSchema = z
.string()
.min(1, 'E-Mail ist erforderlich')
.email('Ungültige E-Mail-Adresse')
.max(254, 'E-Mail-Adresse zu lang');
export const passwordSchema = z
.string()
.min(PASSWORD_MIN_LENGTH, `Passwort muss mindestens ${String(PASSWORD_MIN_LENGTH)} Zeichen lang sein`)
.max(PASSWORD_MAX_LENGTH, `Passwort darf maximal ${String(PASSWORD_MAX_LENGTH)} Zeichen lang sein`)
.refine((val) => /[A-Z]/.test(val), 'Passwort muss mindestens einen Großbuchstaben enthalten')
.refine((val) => /[a-z]/.test(val), 'Passwort muss mindestens einen Kleinbuchstaben enthalten')
.refine((val) => /[0-9]/.test(val), 'Passwort muss mindestens eine Ziffer enthalten');
export const usernameSchema = z
.string()
.min(3, 'Benutzername muss mindestens 3 Zeichen lang sein')
.max(50, 'Benutzername darf maximal 50 Zeichen lang sein')
.regex(/^[a-zA-Z0-9_.-]+$/, 'Benutzername darf nur Buchstaben, Ziffern, _, . und - enthalten');
export const uuidSchema = z.string().uuid('Ungültige UUID');

View file

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false,
"incremental": false
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}