Project Init

This commit is contained in:
Muluhabt
2026-05-29 15:23:46 +03:00
commit 2fbc557aac
67387 changed files with 6063341 additions and 0 deletions

BIN
libs/.DS_Store vendored Normal file

Binary file not shown.

7
libs/api/project.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "@ema-platform/api",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/api/src",
"projectType": "library",
"tags": []
}

3
libs/api/src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './lib/base-api';
export * from './lib/query-and-mutation';
export * from './lib/session';

View File

@@ -0,0 +1,23 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { resolveSessionContext } from '../session';
const BASE_API_URL =
(import.meta as { env?: Record<string, string> }).env?.['VITE_BASE_API_URL'] ??
'http://localhost:3000';
export const baseApi = createApi({
reducerPath: 'baseApi',
baseQuery: fetchBaseQuery({
baseUrl: BASE_API_URL,
prepareHeaders: (headers, { getState }) => {
const { token, sessionHeaders } = resolveSessionContext(
getState() as { auth?: { token?: string } },
);
if (token) headers.set('Authorization', `Bearer ${token}`);
Object.entries(sessionHeaders).forEach(([k, v]) => headers.set(k, v));
return headers;
},
}),
tagTypes: ['Api'],
endpoints: () => ({}),
});

View File

@@ -0,0 +1,44 @@
import { baseApi } from '../base-api';
export type ApiQueryArgs = {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
params?: Record<string, unknown>;
body?: unknown;
headers?: Record<string, string>;
cacheKey?: string | unknown[];
};
const queryApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
apiQuery: builder.query<unknown, ApiQueryArgs>({
query: ({ url, params }) => ({ url, params }),
}),
apiMutation: builder.mutation<unknown, ApiQueryArgs>({
query: ({ url, method = 'POST', body, headers }) => ({
url,
method,
body,
headers,
}),
}),
}),
overrideExisting: false,
});
export const { useApiQueryQuery, useApiMutationMutation } = queryApi;
export function useApiQuery<TData = unknown>(
args: ApiQueryArgs,
options?: Parameters<typeof useApiQueryQuery>[1],
) {
return useApiQueryQuery(args, options) as ReturnType<typeof useApiQueryQuery> & {
data: TData | undefined;
};
}
export function useApiMutation<TData = unknown>() {
return useApiMutationMutation() as ReturnType<typeof useApiMutationMutation> & {
data: TData | undefined;
};
}

View File

@@ -0,0 +1,29 @@
export const SESSION_HEADER_KEYS = {
tenantId: 'x-tenant-id',
organizationUnitId: 'x-organization-unit-id',
currentPositionId: 'x-current-position-id',
currentProjectId: 'x-current-project-id',
} as const;
const TOKEN_STORAGE_KEYS = [
'ema-backoffice-auth-token',
'ema-portal-auth-token',
'auth-token',
] as const;
export function resolveTokenFromStorage(): string | undefined {
for (const key of TOKEN_STORAGE_KEYS) {
const stored = localStorage.getItem(key);
if (stored) return stored;
}
const match = document.cookie.match(/(?:^|;\s*)auth-token=([^;]*)/);
return match ? decodeURIComponent(match[1]) : undefined;
}
export function resolveSessionContext(state?: { auth?: { token?: string } }): {
token: string | undefined;
sessionHeaders: Record<string, string>;
} {
const token = state?.auth?.token ?? resolveTokenFromStorage();
return { token, sessionHeaders: {} };
}

5
libs/api/tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "../../dist/out-tsc" },
"include": ["src/**/*.ts", "src/**/*.tsx"]
}

7
libs/shared/project.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "@ema-platform/shared",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/shared/src",
"projectType": "library",
"tags": []
}

1
libs/shared/src/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './lib/theme/ema-theme';

View File

@@ -0,0 +1,34 @@
import { createTheme, type MantineColorsTuple } from '@mantine/core';
const emaPrimary: MantineColorsTuple = [
'#eff6ff', '#dbeafe', '#bfdbfe', '#93c5fd', '#60a5fa',
'#3b82f6', '#2563eb', '#1d4ed8', '#1e40af', '#1e3a8a',
];
const emaSecondary: MantineColorsTuple = [
'#fdf8f6', '#f2e8e5', '#eaddd7', '#e0cec7', '#d2bab0',
'#bfa094', '#a18072', '#977669', '#65524d', '#2c1f1a',
];
export const emaTheme = createTheme({
primaryColor: 'emaPrimary',
colors: {
emaPrimary,
emaSecondary,
},
fontFamily: 'Inter, sans-serif',
defaultRadius: 'md',
breakpoints: {
xs: '36em',
sm: '48em',
md: '62em',
lg: '75em',
xl: '88em',
},
shadows: {
xs: '0 1px 3px rgba(0,0,0,0.05)',
sm: '0 1px 5px rgba(0,0,0,0.07)',
md: '0 4px 20px rgba(15,23,42,0.08)',
lg: '0 8px 30px rgba(15,23,42,0.12)',
},
});

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "../../dist/out-tsc" },
"include": ["src/**/*.ts", "src/**/*.tsx"]
}

7
libs/ui/project.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "@ema-platform/ui",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/ui/src",
"projectType": "library",
"tags": []
}

3
libs/ui/src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './lib/feedback/ConfirmModal';
export * from './lib/feedback/ApiErrorAlert';
export * from './lib/feedback/notify';

View File

@@ -0,0 +1,24 @@
import { Alert } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react';
interface ApiErrorAlertProps {
error: unknown;
title?: string;
}
export function ApiErrorAlert({ error, title = 'An error occurred' }: ApiErrorAlertProps) {
const message =
error instanceof Error
? error.message
: typeof error === 'object' && error !== null && 'data' in error
? String(
(error as { data: { message?: string } }).data?.message ?? 'Unknown error',
)
: 'Something went wrong. Please try again.';
return (
<Alert icon={<IconAlertCircle size={16} />} title={title} color="red" variant="light">
{message}
</Alert>
);
}

View File

@@ -0,0 +1,39 @@
import { Modal, Button, Group, Text } from '@mantine/core';
interface ConfirmModalProps {
opened: boolean;
onClose: () => void;
onConfirm: () => void;
title?: string;
message?: string;
confirmLabel?: string;
cancelLabel?: string;
loading?: boolean;
}
export function ConfirmModal({
opened,
onClose,
onConfirm,
title = 'Confirm action',
message = 'Are you sure you want to proceed?',
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
loading = false,
}: ConfirmModalProps) {
return (
<Modal opened={opened} onClose={onClose} title={title} size="sm" centered>
<Text size="sm" mb="xl">
{message}
</Text>
<Group justify="flex-end">
<Button variant="subtle" onClick={onClose} disabled={loading}>
{cancelLabel}
</Button>
<Button color="red" onClick={onConfirm} loading={loading}>
{confirmLabel}
</Button>
</Group>
</Modal>
);
}

View File

@@ -0,0 +1,12 @@
import { notifications } from '@mantine/notifications';
export const notify = {
success: (message: string, title = 'Success') =>
notifications.show({ title, message, color: 'green' }),
error: (message: string, title = 'Error') =>
notifications.show({ title, message, color: 'red' }),
info: (message: string, title = 'Info') =>
notifications.show({ title, message, color: 'blue' }),
warning: (message: string, title = 'Warning') =>
notifications.show({ title, message, color: 'yellow' }),
};

5
libs/ui/tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "../../dist/out-tsc" },
"include": ["src/**/*.ts", "src/**/*.tsx"]
}