diff --git a/.dockerignore b/.dockerignore index 3bfcb92..056f5cc 100755 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ node_modules +.next npm-debug.log .git diff --git a/.gitignore b/.gitignore index dc72ce2..339944c 100755 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ yarn-error.log # Platform specific .DS_Store +*.tsbuildinfo + /apps/backend/public/ /apps/backend/storage/ -/apps/backend/.adonisjs/ -/apps/backend/database/schema.ts diff --git a/.husky/pre-push b/.husky/pre-push index cb35684..4bc67a8 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1 +1,3 @@ npm run test:admin +npm run test:backend +npm run test:web diff --git a/.prettierignore b/.prettierignore index baaefbb..51e1eed 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,10 +1,13 @@ -# Dependencies and generated output **/node_modules/** **/build/** **/dist/** **/coverage/** +**/.next/** + apps/backend/public/** apps/backend/storage/** - -# Handlebars generator templates contain application syntax that is not valid HTML. apps/backend/app/templates/** +apps/backend/.adonisjs/** +apps/backend/database/schema.ts + +apps/web/.next/** diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf8820..a0e179f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,56 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added +- Added an `ApiException` base class whose self-handling renders module exceptions as + `{ message, code }` JSON responses, so exception classes no longer need registration in the + global HTTP exception handler. - Added `@crux-kit/editor-tiptap`, a free, headless rich-text editor package built on TipTap. The `light` preset covers bold, italic, underline, strikethrough, headings, lists, blockquote, text alignment, links, and undo/redo, reading and writing content as plain HTML so it themes with the shared design-system tokens. - Added the `InputTiptapEditor` composed form field. +- Added `useIntlDate` and `useIntlName` hooks under `apps/admin/src/app/i18n`, each returning a + formatter bound to the active UI language. Reading the language from context is what subscribes a + component to a language switch; components that read the `IntlDate`/`IntlName` singletons directly ### Changed +- Moved module generation fully server-side. The admin devtools now sends the raw generator config + (`{ name, table, generate, form, datatable }`) instead of pre-rendered source strings, and the + six frontend source builders were ported verbatim from the admin to + `apps/backend/app/services/module_generator`. +- Refactored `module_controller.ts` (~500 lines) into a thin HTTP controller delegating to focused + `module_generator` service modules: an orchestrator, schema-derived context building, naming and + DB-type helpers, a template renderer, and `builders/` + `writers/` subdirectories. +- Split the root `.env` into per-workspace env files (`apps/backend/.env`, `apps/admin/.env`, + `apps/web/.env`) so each app only ingests the variables it uses. Every `docker-compose.yml` + service now loads its own app's env file, and the admin (Vite) and web (Next.js) apps rely on + their frameworks' native env loading instead of custom root-file merging. +- Renamed cross-app URL env variables: `PAGE_MANAGER_API_URL` → `BACKEND_API_URL` in the web app + and `VITE_PAGE_MANAGER_WEB_URL` → `VITE_WEB_URL` in the admin app. +- File Manager exceptions now extend `ApiException`. +- Extracted the admin i18n context out of `I18nProvider` into `app/i18n/context.ts` and + `app/i18n/useLanguage.ts`, so components reach the active language through a side-effect-free + module instead of importing the provider and executing its module body. +- The selected language is now persisted to local storage as its code rather than as the whole + `Language` object. + ### Fixed +- Fixed admin menu highlighting, which marked every ancestor entry active at once. +- Fixes the containing section staying collapsed when a sub-route is opened directly. +- Fixed backend functional test discovery: the `tests/**/*.spec.(ts|js)` globs in `adonisrc.ts` + are not valid for the native `fs.glob` used by Japa v5, so the suite silently ran zero tests. +- Fixed the backend `lint` script path (`../scripts` → `../../scripts`), which made + `npm run lint` fail before linting. +- Removed the stale Japa v1-era `@japa/spec-reporter` wiring from `tests/bootstrap.ts`; the + runner's built-in spec reporter is used instead. +- Renamed `app/exceptions/file_manager_exeptions.ts` to `file_manager_exceptions.ts`, fixing the + filename typo, and updated all imports. + ### Removed +- Removed the root `.env` file. + ## [1.2.0] - 2026-07-17 ### Changed diff --git a/Readme.md b/Readme.md index 8a8deb3..e6f433d 100644 --- a/Readme.md +++ b/Readme.md @@ -1,3 +1,18 @@ +# CRUX + +A monorepo starter for products that need an admin panel, an API, and a public website. It ships an +AdonisJS backend, a React admin panel with a module generator, and a Next.js website that renders +the pages editors publish through the built-in CMS. + +| App | Source | Dev port | What it is | +| --------------- | --------------- | -------- | ------------------------------------------------------ | +| Backend API | `apps/backend/` | 3333 | AdonisJS — data, auth, permissions, public content API | +| Admin panel | `apps/admin/` | 5173 | React + Vite — the internal UI | +| Public website | `apps/web/` | 3000 | Next.js — the site visitors see | +| Shared packages | `packages/` | — | UI, hooks, services, types, utils | + +Full documentation lives in [`docs/`](./docs/Readme.md). + ### Tech Stack #### Backend @@ -6,94 +21,157 @@ - TypeScript - Lucid ORM - PostgreSQL / MySQL (configurable) +- Redis, S3 / MinIO storage -#### Frontend +#### Admin panel - React - TypeScript - Vite -- React Query / Mobx / Axios -- Component-based UI system +- React Query / MobX / Axios +- Radix UI + Tailwind component system +- React Hook Form + Zod + +#### Public website + +- Next.js (App Router, React Server Components) +- TypeScript +- next-intl +- Tailwind CSS ### Getting Started #### Prerequisites -Node.js >= v24.18.0 -npm >= 11.15.0 -Database PostgreSQL +- Node.js `24.18.0` (see `.nvmrc`; the package manifest requires `>=24.0.0`) +- npm `>=11.6.2` (the repo pins `npm@11.15.0`) +- PostgreSQL #### Installation -``` +```bash git clone gitlab@git.rexsoftproduction.com:crux/admin.git -cd crux -npm install ``` +```bash +cd crux && npm install --force +``` + +> `--force` is required. `typescript-eslint` does not yet declare support for the TypeScript +> version this repo uses, so a plain `npm install` fails with `ERESOLVE`. Never substitute +> `--legacy-peer-deps` — it strips nested peer dependencies and breaks the backend at runtime. Use +> `npm ci --force` for a reproducible install, and `npm install --force` when adding one. +> See [troubleshooting.md](./troubleshooting.md). + ### Configuration -Environment variables are defined in root `.env` +Each workspace defines its own environment variables. Copy the workspace's `.env.example` to `.env` and fill in the values: -``` -TZ=UTC -PORT=3333 -HOST=localhost -LOG_LEVEL=info -APP_KEY= -NODE_ENV=development -DB_HOST=127.0.0.1 -DB_PORT=5432 -DB_USER=postgres -DB_PASSWORD=postgres -DB_DATABASE=adonis -VITE_API_URL=http://0.0.0.0:3333 -VITE_TINYMCE_API_KEY= +| Env file | Covers | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apps/backend/.env` | Server (`PORT`, `APP_KEY`), database (`DB_*`), storage (`AWS_*`, `S3_*`), and the public site link (`WEB_APP_URL`, `WEB_REVALIDATE_SECRET`) used to purge its cache | +| `apps/admin/.env` | `VITE_API_URL` (backend), `VITE_WEB_URL` (public site, used for page previews), `VITE_TINYMCE_API_KEY` | +| `apps/web/.env` | `BACKEND_API_URL` (backend base URL for server rendering), `REVALIDATE_SECRET` (must match the backend's `WEB_REVALIDATE_SECRET`), `SITE_URL` (canonical origin) | + +`docker-compose.yml` loads each service's env from the same per-app files. Inside Docker, service +URLs must use compose service names (`http://backend:3333`), not `localhost`. + +### Running + +#### Locally + +| Command | Description | +| ----------------------- | ----------------------------------------------- | +| `npm run dev:backend` | Start the AdonisJS backend in development mode | +| `npm run dev:admin-app` | Start the React admin application | +| `npm run dev:web` | Start the Next.js web application | +| `npm run dev:all` | Start backend, admin, and web apps concurrently | + +#### With Docker + +```bash +docker compose up -d ``` -### Module Generator +Brings up everything: backend (3333), admin (5173), web (3000), PostgreSQL (5432), Redis (6379), +MinIO (9000 / console 9001) and pgAdmin (5050). -The module generator allows you to create full CRUD modules by selecting: +Two quirks worth knowing when a change appears not to take effect: + +- The web container keeps `.next` on an anonymous volume that survives rebuilds. Clear it with + `docker compose up -d --force-recreate --renew-anon-volumes web`. +- Backend HMR does not always pick up edits on macOS (bind-mount file events); restart + `crux_backend_adonis` to load them. + +### Scripts + +#### Build + +| Command | Description | +| ----------------------- | ------------------------------------------ | +| `npm run build:backend` | Build the backend application | +| `npm run build:admin` | Type-check and build the admin application | +| `npm run build:web` | Type-check and build the Next.js web app | + +#### Test + +| Command | Description | +| ---------------------- | ---------------------------------------------- | +| `npm run test:backend` | Backend functional tests (requires a database) | +| `npm run test:admin` | Admin unit tests (Vitest) | +| `npm run test:web` | Web app unit tests (Vitest) | + +#### Lint and format + +| Command | Description | +| ---------------------- | -------------------------- | +| `npm run lint` | ESLint across the monorepo | +| `npm run lint:fix` | ESLint with autofix | +| `npm run format` | Prettier write | +| `npm run format:check` | Prettier check, no writes | + +#### Versions + +| Command | Description | +| ----------------------- | ------------------------------------ | +| `npm run version:patch` | Update patch version of all packages | +| `npm run version:minor` | Update minor version of all packages | +| `npm run version:major` | Update major version of all packages | + +> Versions are updated without creating git tags. + +### Features + +#### Module Generator + +Create full CRUD modules by selecting: - Database table - Fields and types - Validation rules - List view configuration -#### Generated Output +**Generated output** — Backend: controllers, validators, routes. Frontend: pages, forms, tables, API. -**Backend**: controllers, validators, routes -**Frontend**: pages, forms, tables, API +#### Authentication & Authorization -### Authentication & Authorization - -- JWT / Session-based authentication +- JWT / session-based authentication - Role & permission system - Policy-based access control -### Scripts +#### Content Management -#### Development +Web Manager is the built-in CMS: editors build localized pages from prebuilt blocks in the admin +panel, preview them privately, and publish to the Next.js site, which purges only the cache entries +the change affects. See [Web Manager](./docs/core/web-manager/Readme.md). -| Command | Description | -| ----------------------- | ---------------------------------------------- | -| `npm run dev:backend` | Start the AdonisJS backend in development mode | -| `npm run dev:admin-app` | Start the React admin application | -| `npm run dev:all` | Start backend and admin app concurrently | +### Documentation -#### Build - -| Command | Description | -| ----------------------- | ----------------------------- | -| `npm run build:backend` | Build the backend application | -| `npm run build:admin` | Build the admin application | - -#### Versions - -| Command | Description | -| --------------- | ------------------------------------ | -| `npm run patch` | Update Patch version of all packages | -| `npm run minor` | Update Minor version of all packages | -| `npm run major` | Update Major version of all packages | - -> Versions are updated without creating git tags. +| Topic | Where | +| ------------------------ | ---------------------------------------------------- | +| Documentation home | [docs/Readme.md](./docs/Readme.md) | +| Public website | [apps/web/Readme.md](./apps/web/Readme.md) | +| Architecture | [docs/architecture/](./docs/architecture/Readme.md) | +| Core modules | [docs/core/](./docs/Readme.md#core-modules) | +| Shared packages | [docs/packages/Readme.md](./docs/packages/Readme.md) | +| Install and setup issues | [troubleshooting.md](./troubleshooting.md) | diff --git a/apps/admin/.env.example b/apps/admin/.env.example index a0d69f9..a1aa4c7 100755 --- a/apps/admin/.env.example +++ b/apps/admin/.env.example @@ -1,2 +1,3 @@ -VITE_API_URL=http://0.0.0.0:3333 -VITE_TINYMCE_API_KEY= \ No newline at end of file +VITE_API_URL=http://localhost:3333 +VITE_TINYMCE_API_KEY= +VITE_WEB_URL=http://localhost:3000 \ No newline at end of file diff --git a/apps/admin/README.md b/apps/admin/README.md deleted file mode 100755 index 6d80aee..0000000 --- a/apps/admin/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Metronic 9 | All-in-One Tailwind based HTML/React/Next.js Template for Modern Web Applications - -### Prerequisites - -- Node.js 22.14.0 or higher -- Npm or Yarn -- Tailwind CSS 4.x -- React 19.x diff --git a/apps/admin/public/media/app/web-studio-dark.svg b/apps/admin/public/media/app/web-studio-dark.svg new file mode 100644 index 0000000..73e0edf --- /dev/null +++ b/apps/admin/public/media/app/web-studio-dark.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/apps/admin/public/media/app/web-studio-light.svg b/apps/admin/public/media/app/web-studio-light.svg new file mode 100644 index 0000000..c07b85e --- /dev/null +++ b/apps/admin/public/media/app/web-studio-light.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/apps/admin/public/media/web-studio/blocks/articles.svg b/apps/admin/public/media/web-studio/blocks/articles.svg new file mode 100644 index 0000000..d33dcdf --- /dev/null +++ b/apps/admin/public/media/web-studio/blocks/articles.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/public/media/web-studio/blocks/banner.svg b/apps/admin/public/media/web-studio/blocks/banner.svg new file mode 100644 index 0000000..f90a075 --- /dev/null +++ b/apps/admin/public/media/web-studio/blocks/banner.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/admin/public/media/web-studio/blocks/content.svg b/apps/admin/public/media/web-studio/blocks/content.svg new file mode 100644 index 0000000..23a4516 --- /dev/null +++ b/apps/admin/public/media/web-studio/blocks/content.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/public/media/web-studio/blocks/faq.svg b/apps/admin/public/media/web-studio/blocks/faq.svg new file mode 100644 index 0000000..1c023ad --- /dev/null +++ b/apps/admin/public/media/web-studio/blocks/faq.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/public/media/web-studio/blocks/media-content.svg b/apps/admin/public/media/web-studio/blocks/media-content.svg new file mode 100644 index 0000000..0e2cec0 --- /dev/null +++ b/apps/admin/public/media/web-studio/blocks/media-content.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/admin/public/media/web-studio/blocks/media.svg b/apps/admin/public/media/web-studio/blocks/media.svg new file mode 100644 index 0000000..3ccb2b4 --- /dev/null +++ b/apps/admin/public/media/web-studio/blocks/media.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/admin/src/app/i18n/context.ts b/apps/admin/src/app/i18n/context.ts new file mode 100644 index 0000000..4b79daf --- /dev/null +++ b/apps/admin/src/app/i18n/context.ts @@ -0,0 +1,4 @@ +import { createContext } from 'react'; +import { type I18nProviderProps } from '#app/i18n/types'; + +export default createContext(null); diff --git a/apps/admin/src/app/i18n/messages/ar.json b/apps/admin/src/app/i18n/messages/ar.json index 3920d3f..61ae882 100755 --- a/apps/admin/src/app/i18n/messages/ar.json +++ b/apps/admin/src/app/i18n/messages/ar.json @@ -1,7 +1,105 @@ { + "ACTION.BACK": "رجوع", + "ACTION.SAVE": "حفظ", + "ACTION.CREATE": "إنشاء", + "ACTION.INCLUDE": "تضمين", + "ACTION.GENERATE": "توليد", + "ACTION.REMOVE": "إزالة", + "ACTION.CANCEL": "إلغاء", + "ACTION.CONFIRM": "تأكيد", + "ACTION.SUBMIT": "إرسال", + "ACTION.SEARCH": "بحث", + "ACTION.SWAP": "تبديل", + "ACTION.MERGE": "دمج", + "ACTION.SPLIT": "تقسيم", + "ACTION.RESET": "إعادة تعيين", + "ACTION.PREVIEW": "معاينة", + "ACTION.UPLOAD": "رفع", + "ACTION.DELETE": "حذف", + "ACTION.CLOSE": "إغلاق", + "ACTION.CLEAR": "مسح", + "ACTION.CLEAR_FILTERS": "مسح عوامل التصفية", + "ACTION.EDIT": "تعديل", + "STATE.CREATING": "جارٍ الإنشاء", + "STATE.GENERATING": "جارٍ التوليد", + "STATE.SAVING": "جارٍ الحفظ", + "STATE.REMOVING": "جارٍ الإزالة", + "STATE.LOADING": "جارٍ التحميل", + "STATE.UPDATING": "جارٍ التحديث", + "ERROR": "خطأ", + "PERMISSION": "صلاحية", + "PERMISSION.PERMISSIONS": "الصلاحيات", + "PERMISSION.PERMISSION_NAME": "اسم الصلاحية", + "PERMISSION.SLUG": "Slug", + "PERMISSION.EDIT_PERMISSION": "تعديل الصلاحية", + "ROLE": "دور", + "ROLE.ROLES": "الأدوار", + "ROLE.ROLE_NAME": "اسم الدور", + "ROLE.SLUG": "Slug", + "ROLE.EDIT_ROLE": "تعديل الدور", + "USER": "مستخدم", + "USER.EDIT_USER": "تعديل المستخدم", + "USER.STATUS.ACTIVE": "نشط", + "USER.STATUS.INACTIVE": "غير نشط", "USER.MENU.MY_PROFILE": "الملف الشخصي الخاص بي", "USER.MENU.MY_ACCOUNT": "حسابي", + "USER.MENU.LANGUAGE": "لغة", "USER.MENU.DARK_MODE": "الوضع المظلم", "USER.MENU.LOGOUT": "تسجيل الخروج", - "USER.MENU.LANGUAGE": "لغة" + "SUCCESS": "تم بنجاح", + "ACTIONS": "الإجراءات", + "FILTER": "تصفية", + "SELECTED": "محدد", + "NAME": "الاسم", + "FIRST_NAME": "الاسم الأول", + "LAST_NAME": "اسم العائلة", + "EMAIL": "البريد الإلكتروني", + "SELECT": "اختر", + "SELECT_OPTION": "اختر خياراً", + "SELECT_ALL": "تحديد الكل", + "SELECT_ALL_WITH_OPTS_LENGTH": "تحديد الكل ({length} خيارات)", + "STATUS": "الحالة", + "PASSWORD": "كلمة المرور", + "NEW_PASSWORD": "كلمة المرور الجديدة", + "PASSWORD_CONFIRM": "تأكيد كلمة المرور", + "MODULE_NAME": "اسم الوحدة", + "RETURN_HOME": "العودة إلى الرئيسية", + "NO_ITEMS_FOUND": "لم يتم العثور على عناصر", + "NO_RESULTS_FOUND": "لم يتم العثور على نتائج", + "PLUS_MORE_WITH_NUMBER": "+ {number} أخرى", + "ADMIN.ADMIN_MANAGEMENT": "إدارة المشرفين", + "ADMIN.ADMINS": "المشرفون", + "DASHBOARD": "لوحة التحكم", + "FORM.ERROR.REQUIRED": "مطلوب", + "FORM.ERROR.INVALID_EMAIL_ADDRESS": "عنوان بريد إلكتروني غير صالح", + "FORM.ERROR.PASSWORDS_MUST_MATCH": "يجب أن تتطابق كلمتا المرور", + "FORM.ERROR.MIN_PASSWORD_LENGTH_DESC": "الحد الأدنى لطول كلمة المرور - 8 أحرف.", + "FORM.ERROR.SOMETHING_WENT_WRONG": "حدث خطأ ما", + "TABLE.COMMON.COLUMNS": "الأعمدة", + "TABLE.COMMON.NO_DATA_AVAILABLE": "لا توجد بيانات متاحة", + "TABLE.COMMON.ROWS_PER_PAGE": "عدد الصفوف في الصفحة", + "TABLE.COMMON.GO_TO_PREVIOUS_PAGE": "الانتقال إلى الصفحة السابقة", + "TABLE.COMMON.GO_TO_NEXT_PAGE": "الانتقال إلى الصفحة التالية", + "PROFILE.PROFILE_SETTINGS": "إعدادات الملف الشخصي", + "PROFILE.AVATAR_DESCRIPTION": "نوصي باستخدام صور لا تقل أبعادها عن 98 × 98 بكسل (بحجم يصل إلى 4 ميغابايت) بصيغة PNG أو JPG أو JPEG.", + "PROFILE.AVATAR_ALT": "الصورة الرمزية", + "PROFILE.AVATAR_SETTINGS": "إعدادات الصورة الرمزية", + "PROFILE.AVATAR_CROP_ME": "قص الصورة", + "PROFILE.EDIT_PROFILE_IMAGE": "تعديل صورة الملف الشخصي", + "MODULE.COMMON.REMOVE_ITEM_QUESTION": "هل أنت متأكد من رغبتك في إزالة {target}؟", + "MODULE.COMMON.ARE_YOU_SURE?": "هل أنت متأكد؟", + "MODULE.COMMON.REMOVE_ITEM_QUESTION_DESC": "لا يمكن التراجع عن هذا الإجراء.", + "MODULE.COMMON.CREATE_NEW_ENTITY": "إنشاء {entity} جديد", + "MODULE.COMMON.CREATE_NEW_USER": "إنشاء مستخدم جديد", + "MESSAGE.PLEASE_WAIT": "يرجى الانتظار", + "MESSAGE.PLEASE_WAIT_THIS_MAY_TAKE_FEW_MOMENTS": "يرجى الانتظار، قد يستغرق هذا بعض الوقت", + "COMMON.SELECT_FILE_TO_VIEW_ITS_CONTENT": "اختر ملفاً لعرض محتوياته", + "COMPONENT.FILE_INPUT.SELECTED_NUM_OF_FILES": "تم اختيار {num} ملف", + "COMPONENT.FILE_INPUT.SELECT_FILE_NO_FILE_SELECTED": "اختر ملفاً، لم يتم اختيار أي ملف", + "COMPONENT.FILE_INPUT.OPTION.FROM_CLOUD_STORAGE": "من التخزين السحابي", + "COMPONENT.FILE_INPUT.OPTION.FROM_LOCAL_MACHINE": "من الجهاز المحلي", + "COMPONENT.FILE_INPUT.OPTION.CLEAR": "مسح", + "COMPONENT.FILE_INPUT.SHOW_SELECTED_FILES": "عرض الملفات المختارة", + "COMPONENT.FILE_INPUT.SELECTED_FILES": "الملفات المختارة", + "COMPONENT.FILE_INPUT.REMOVE_NAME": "إزالة {name}" } diff --git a/apps/admin/src/app/i18n/messages/en.json b/apps/admin/src/app/i18n/messages/en.json index 5da3346..5ba984d 100755 --- a/apps/admin/src/app/i18n/messages/en.json +++ b/apps/admin/src/app/i18n/messages/en.json @@ -20,28 +20,23 @@ "ACTION.CLEAR": "Clear", "ACTION.CLEAR_FILTERS": "Clear filters", "ACTION.EDIT": "Edit", - "STATE.CREATING": "Creating", "STATE.GENERATING": "Generating", "STATE.SAVING": "Saving", "STATE.REMOVING": "Removing", "STATE.LOADING": "Loading", "STATE.UPDATING": "Updating", - "ERROR": "Error", - "PERMISSION": "Permission", "PERMISSION.PERMISSIONS": "Permissions", "PERMISSION.PERMISSION_NAME": "Permission name", "PERMISSION.SLUG": "Slug", "PERMISSION.EDIT_PERMISSION": "Edit permission", - "ROLE": "Role", "ROLE.ROLES": "Roles", "ROLE.ROLE_NAME": "Role name", "ROLE.SLUG": "Slug", "ROLE.EDIT_ROLE": "Edit role", - "USER": "User", "USER.EDIT_USER": "Edit user", "USER.STATUS.ACTIVE": "Active", @@ -51,7 +46,6 @@ "USER.MENU.LANGUAGE": "Language", "USER.MENU.DARK_MODE": "Dark Mode", "USER.MENU.LOGOUT": "Logout", - "SUCCESS": "Success", "ACTIONS": "Actions", "FILTER": "Filter", @@ -73,42 +67,33 @@ "NO_ITEMS_FOUND": "No items found", "NO_RESULTS_FOUND": "No results found", "PLUS_MORE_WITH_NUMBER": "+ {number} more", - "ADMIN.ADMIN_MANAGEMENT": "Admin Management", "ADMIN.ADMINS": "Admins", - "DASHBOARD": "Dashboard", - "FORM.ERROR.REQUIRED": "Required", "FORM.ERROR.INVALID_EMAIL_ADDRESS": "Invalid email address", "FORM.ERROR.PASSWORDS_MUST_MATCH": "Passwords must match", "FORM.ERROR.MIN_PASSWORD_LENGTH_DESC": "Min. password length - 8 chars.", "FORM.ERROR.SOMETHING_WENT_WRONG": "Something went wrong", - "TABLE.COMMON.COLUMNS": "Columns", "TABLE.COMMON.NO_DATA_AVAILABLE": "No data available", "TABLE.COMMON.ROWS_PER_PAGE": "Rows per page", "TABLE.COMMON.GO_TO_PREVIOUS_PAGE": "Go to previous page", "TABLE.COMMON.GO_TO_NEXT_PAGE": "Go to next page", - "PROFILE.PROFILE_SETTINGS": "Profile Settings", "PROFILE.AVATAR_DESCRIPTION": "We recommend using images that are at least 98 x 98 pixels in size (file size up to 4 MB) in PNG, JPG, JPEG format.", "PROFILE.AVATAR_ALT": "Avatar", "PROFILE.AVATAR_SETTINGS": "Avatar Settings", "PROFILE.AVATAR_CROP_ME": "Crop me", "PROFILE.EDIT_PROFILE_IMAGE": "Edit profile image", - "MODULE.COMMON.REMOVE_ITEM_QUESTION": "Are you sure you want to remove {target}?", "MODULE.COMMON.ARE_YOU_SURE?": "Are you sure?", "MODULE.COMMON.REMOVE_ITEM_QUESTION_DESC": "This action cannot be undone.", "MODULE.COMMON.CREATE_NEW_ENTITY": "Create new {entity}", "MODULE.COMMON.CREATE_NEW_USER": "Create new user", - "MESSAGE.PLEASE_WAIT": "Please wait", "MESSAGE.PLEASE_WAIT_THIS_MAY_TAKE_FEW_MOMENTS": "Please wait, this may take few moments", - "COMMON.SELECT_FILE_TO_VIEW_ITS_CONTENT": "Select a file to view its contents", - "COMPONENT.FILE_INPUT.SELECTED_NUM_OF_FILES": "{num} file(s) selected", "COMPONENT.FILE_INPUT.SELECT_FILE_NO_FILE_SELECTED": "Select file, no file selected", "COMPONENT.FILE_INPUT.OPTION.FROM_CLOUD_STORAGE": "From cloud storage", diff --git a/apps/admin/src/app/i18n/messages/uk.json b/apps/admin/src/app/i18n/messages/uk.json index 6326e25..a62db50 100755 --- a/apps/admin/src/app/i18n/messages/uk.json +++ b/apps/admin/src/app/i18n/messages/uk.json @@ -20,28 +20,23 @@ "ACTION.CLEAR": "Очистити", "ACTION.CLEAR_FILTERS": "Очистити фільтри", "ACTION.EDIT": "Редагувати", - "STATE.CREATING": "Створення", "STATE.GENERATING": "Генерування", "STATE.SAVING": "Збереження", "STATE.REMOVING": "Видалення", "STATE.LOADING": "Завантаження", "STATE.UPDATING": "Оновлюється", - "ERROR": "Помилка", - "PERMISSION": "Дозвіл", "PERMISSION.PERMISSIONS": "Дозволи", "PERMISSION.PERMISSION_NAME": "Назва дозволу", "PERMISSION.SLUG": "Slug", "PERMISSION.EDIT_PERMISSION": "Редагувати дозвіл", - "ROLE": "Роль", "ROLE.ROLES": "Ролі", "ROLE.ROLE_NAME": "Назва ролі", "ROLE.SLUG": "Slug", "ROLE.EDIT_ROLE": "Редагувати роль", - "USER": "Користувач", "USER.EDIT_USER": "Редагувати користувача", "USER.STATUS.ACTIVE": "Активний", @@ -51,7 +46,6 @@ "USER.MENU.LANGUAGE": "Мова", "USER.MENU.DARK_MODE": "Темний режим", "USER.MENU.LOGOUT": "Вийти", - "SUCCESS": "Успіх", "ACTIONS": "Дії", "FILTER": "Фільтр", @@ -73,42 +67,33 @@ "NO_ITEMS_FOUND": "Не знайдено жодного елемента", "NO_RESULTS_FOUND": "Результатів не знайдено", "PLUS_MORE_WITH_NUMBER": "+ {number} ще", - "ADMIN.ADMIN_MANAGEMENT": "Адмін Управління", "ADMIN.ADMINS": "Адміністратори", - "DASHBOARD": "Дашборд", - "FORM.ERROR.REQUIRED": "Обов'язково", "FORM.ERROR.INVALID_EMAIL_ADDRESS": "Недійсна адреса електронної пошти", "FORM.ERROR.PASSWORDS_MUST_MATCH": "Паролі мають збігатися", "FORM.ERROR.MIN_PASSWORD_LENGTH_DESC": "Мінімальна довжина пароля - 8 символів.", "FORM.ERROR.SOMETHING_WENT_WRONG": "Щось пішло не так", - "TABLE.COMMON.COLUMNS": "Колонки", "TABLE.COMMON.NO_DATA_AVAILABLE": "Немає доступних даних", "TABLE.COMMON.ROWS_PER_PAGE": "Рядків на сторінці", "TABLE.COMMON.GO_TO_PREVIOUS_PAGE": "Перейти на попередню сторінку", "TABLE.COMMON.GO_TO_NEXT_PAGE": "Перейти на наступну сторінку", - "PROFILE.PROFILE_SETTINGS": "Налаштування профілю", "PROFILE.AVATAR_DESCRIPTION": "Радимо використовувати зображення розміром принаймні 98 x 98 пікселів (розмір файлу - до 4 МБ) у форматі PNG, JPG , JPEG.", "PROFILE.AVATAR_ALT": "Аватар", "PROFILE.AVATAR_SETTINGS": "Налаштування аватара", "PROFILE.AVATAR_CROP_ME": "Обріж мене", "PROFILE.EDIT_PROFILE_IMAGE": "Редагувати зображення профілю", - "MODULE.COMMON.REMOVE_ITEM_QUESTION": "Ви впевнені, що хочете видалити {target}?", "MODULE.COMMON.ARE_YOU_SURE?": "Ви впевнені?", "MODULE.COMMON.REMOVE_ITEM_QUESTION_DESC": "Цю дію не можна скасувати.", "MODULE.COMMON.CREATE_NEW_ENTITY": "Створити новий {entity}", "MODULE.COMMON.CREATE_NEW_USER": "Створити нового користувача", - "MESSAGE.PLEASE_WAIT": "Будь ласка, зачекайте", "MESSAGE.PLEASE_WAIT_THIS_MAY_TAKE_FEW_MOMENTS": "Зачекайте хвилинку, це може зайняти деякий час", - "COMMON.SELECT_FILE_TO_VIEW_ITS_CONTENT": "Виберіть файл, щоб переглянути його вміст", - "COMPONENT.FILE_INPUT.SELECTED_NUM_OF_FILES": "Вибрано {num} файл(и)", "COMPONENT.FILE_INPUT.SELECT_FILE_NO_FILE_SELECTED": "Виберіть файл, файл не вибрано", "COMPONENT.FILE_INPUT.OPTION.FROM_CLOUD_STORAGE": "З хмарного сховища", diff --git a/apps/admin/src/app/i18n/useIntlDate.ts b/apps/admin/src/app/i18n/useIntlDate.ts new file mode 100644 index 0000000..12797b5 --- /dev/null +++ b/apps/admin/src/app/i18n/useIntlDate.ts @@ -0,0 +1,20 @@ +import { useMemo } from 'react'; +import useLanguage from '#app/i18n/useLanguage'; +import { IntlDate } from '#infrastructure/IntlDate'; + +type DisplayFormat = Parameters[0]; +type SourceFormat = Parameters[1]; + +const useIntlDate = () => { + const { currenLanguage } = useLanguage(); + + return useMemo( + () => + (value: string, format: DisplayFormat, sourceFormat: SourceFormat = 'UNIVERSAL_ISO') => + IntlDate.parseSource(value, sourceFormat).formatToDisplay(format), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currenLanguage.code] + ); +}; + +export default useIntlDate; diff --git a/apps/admin/src/app/i18n/useIntlName.ts b/apps/admin/src/app/i18n/useIntlName.ts new file mode 100644 index 0000000..6bb9718 --- /dev/null +++ b/apps/admin/src/app/i18n/useIntlName.ts @@ -0,0 +1,18 @@ +import { useMemo } from 'react'; +import useLanguage from '#app/i18n/useLanguage'; +import { IntlName } from '#infrastructure/IntlName'; + +type NameInput = Parameters[0]; +type NameFormat = Parameters[1]; + +const useIntlName = () => { + const { currenLanguage } = useLanguage(); + + return useMemo( + () => (input: NameInput, format: NameFormat) => IntlName.formatToDisplay(input, format), + // eslint-disable-next-line react-hooks/exhaustive-deps + [currenLanguage.code] + ); +}; + +export default useIntlName; diff --git a/apps/admin/src/app/i18n/useLanguage.ts b/apps/admin/src/app/i18n/useLanguage.ts new file mode 100644 index 0000000..4352a3e --- /dev/null +++ b/apps/admin/src/app/i18n/useLanguage.ts @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import TranslationsContext from '#app/i18n/context'; + +const useLanguage = () => { + const context = useContext(TranslationsContext); + + if (!context) throw new Error('useLanguage must be used within I18nProvider'); + + return context; +}; + +export default useLanguage; diff --git a/apps/admin/src/app/layouts/01__Default/components/SidebarMenu.tsx b/apps/admin/src/app/layouts/01__Default/components/SidebarMenu.tsx index 7d53e44..65be97e 100755 --- a/apps/admin/src/app/layouts/01__Default/components/SidebarMenu.tsx +++ b/apps/admin/src/app/layouts/01__Default/components/SidebarMenu.tsx @@ -1,10 +1,11 @@ 'use client'; -import { JSX, useCallback } from 'react'; +import { JSX, useMemo } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { cn } from '@crux-kit/utils'; import { MenuConfig, MenuItem } from '#app/config/types'; import { useMenuConfig } from '#app/menu'; +import { createMenuPathMatcher } from '#shared/lib/menuPath'; import { AccordionMenu, AccordionMenuClassNames, AccordionMenuGroup, AccordionMenuItem, AccordionMenuLabel, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '#shared/ui/AccordionMenu'; import { Badge } from '#shared/ui/Badge'; @@ -13,9 +14,9 @@ export function SidebarMenu() { const menuConfig = useMenuConfig(); // Memoize matchPath to prevent unnecessary re-renders - const matchPath = useCallback( - (path: string): boolean => path === pathname || (path.length > 1 && pathname.startsWith(path)), - [pathname] + const matchPath = useMemo( + () => createMenuPathMatcher(menuConfig, pathname), + [menuConfig, pathname] ); // Global classNames for consistent styling diff --git a/apps/admin/src/app/layouts/02__HorizontalMenu/components/NavbarMenu.tsx b/apps/admin/src/app/layouts/02__HorizontalMenu/components/NavbarMenu.tsx index 6990be0..162a07d 100755 --- a/apps/admin/src/app/layouts/02__HorizontalMenu/components/NavbarMenu.tsx +++ b/apps/admin/src/app/layouts/02__HorizontalMenu/components/NavbarMenu.tsx @@ -1,9 +1,11 @@ import { ChevronDown } from 'lucide-react'; +import { useMemo } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { cn } from '@crux-kit/utils'; import { MenuConfig } from '#app/config/types'; import { useMenuConfig } from '#app/menu'; import { useMenu } from '#shared/hooks/useMenu'; +import { createMenuPathMatcher } from '#shared/lib/menuPath'; import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from '#shared/ui/MenuBar'; export function NavbarMenu() { @@ -24,7 +26,12 @@ export function NavbarMenu() { navbarMenu = menuConfig?.[3]; } - const { isActive, hasActiveChild } = useMenu(pathname); + const { hasActiveChild } = useMenu(pathname); + // A leaf highlights only when nothing deeper in the menu matches the route more closely. + const isActive = useMemo( + () => createMenuPathMatcher((navbarMenu?.children ?? []) as MenuConfig, pathname), + [navbarMenu, pathname] + ); const buildMenu = (items: MenuConfig) => { return items.map((item, index) => { diff --git a/apps/admin/src/app/layouts/04__TwoColumnSidebar/components/SidebarMenuDefault.tsx b/apps/admin/src/app/layouts/04__TwoColumnSidebar/components/SidebarMenuDefault.tsx index a7de4d9..5e70a92 100755 --- a/apps/admin/src/app/layouts/04__TwoColumnSidebar/components/SidebarMenuDefault.tsx +++ b/apps/admin/src/app/layouts/04__TwoColumnSidebar/components/SidebarMenuDefault.tsx @@ -1,21 +1,23 @@ 'use client'; -import { JSX, useCallback } from 'react'; +import { JSX, useMemo } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { cn } from '@crux-kit/utils'; import { MenuConfig, MenuItem } from '#app/config/types'; import { useMenuConfig } from '#app/menu'; import { useMenu } from '#shared/hooks/useMenu'; +import { createMenuPathMatcher } from '#shared/lib/menuPath'; import { AccordionMenu, AccordionMenuClassNames, AccordionMenuGroup, AccordionMenuItem, AccordionMenuLabel, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '#shared/ui/AccordionMenu'; import { Badge } from '#shared/ui/Badge'; export function SidebarMenuDefault() { const { pathname } = useLocation(); + const fullMenuConfig = useMenuConfig(); // Memoize matchPath to prevent unnecessary re-renders - const matchPath = useCallback( - (path: string): boolean => path === pathname || (path.length > 1 && pathname.startsWith(path)), - [pathname] + const matchPath = useMemo( + () => createMenuPathMatcher(fullMenuConfig, pathname), + [fullMenuConfig, pathname] ); // Global classNames for consistent styling @@ -165,8 +167,7 @@ export function SidebarMenuDefault() { // Memoize the menu to prevent unnecessary re-renders const { getChildren } = useMenu(pathname); - const menuConfig = useMenuConfig(); - const menuItems = getChildren(menuConfig, 0); + const menuItems = getChildren(fullMenuConfig, 0); return ( path === pathname || (path.length > 1 && pathname.startsWith(path)), - [pathname] + const matchPath = useMemo( + () => createMenuPathMatcher(menuConfig, pathname), + [menuConfig, pathname] ); // Global classNames for consistent styling diff --git a/apps/admin/src/app/layouts/06__MultipleMenus/components/Header.tsx b/apps/admin/src/app/layouts/06__MultipleMenus/components/Header.tsx index c77dab8..d049bfa 100755 --- a/apps/admin/src/app/layouts/06__MultipleMenus/components/Header.tsx +++ b/apps/admin/src/app/layouts/06__MultipleMenus/components/Header.tsx @@ -15,7 +15,6 @@ const Header = () => { const [isSheetOpen, setIsSheetOpen] = useState(false); const isMobile = useIsMobile(); - // Close sheet when route changes useEffect(() => { setIsSheetOpen(false); }, [pathname]); diff --git a/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarFooter.tsx b/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarFooter.tsx index 108c8c5..899cb07 100755 --- a/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarFooter.tsx +++ b/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarFooter.tsx @@ -1,10 +1,11 @@ -import { IntlName } from '#infrastructure/IntlName'; +import useIntlName from '#app/i18n/useIntlName'; import { useAuth } from '#core/Auth/providers/AuthProvider'; import { Avatar, AvatarFallback, AvatarImage } from '#shared/ui/Avatar'; import { UserDropdownMenu } from '#shared/widgets/UserDropdownMenu'; export function SidebarFooter() { const { user } = useAuth(); + const formatName = useIntlName(); return (
@@ -13,9 +14,7 @@ export function SidebarFooter() { trigger={ - - {IntlName.formatToDisplay(user, 'INITIALS_LF')} - + {formatName(user, 'INITIALS_LF')} } /> diff --git a/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarMenuPrimary.tsx b/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarMenuPrimary.tsx index 3b17565..e2a1499 100755 --- a/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarMenuPrimary.tsx +++ b/apps/admin/src/app/layouts/06__MultipleMenus/components/SidebarMenuPrimary.tsx @@ -1,25 +1,27 @@ 'use client'; -import { JSX, useCallback } from 'react'; +import { Fragment, JSX, useMemo } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { cn } from '@crux-kit/utils'; import { MenuConfig, MenuItem } from '#app/config/types'; import { useMenuConfig } from '#app/menu'; import { useAuth } from '#core/Auth/providers/AuthProvider'; +import { createMenuPathMatcher } from '#shared/lib/menuPath'; import { AccordionMenu, AccordionMenuClassNames, AccordionMenuGroup, AccordionMenuItem, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '#shared/ui/AccordionMenu'; import { getPermittedMenuItems } from '../lib'; export function SidebarMenuPrimary() { const { pathname } = useLocation(); const { user } = useAuth(); + const menuConfig = useMenuConfig(); + const userPermissions = user?.permissions ?? []; - // Memoize matchPath to prevent unnecessary re-renders - const matchPath = useCallback( - (path: string): boolean => path === pathname || (path.length > 1 && pathname.startsWith(path)), - [pathname] + + const matchPath = useMemo( + () => createMenuPathMatcher(menuConfig, pathname), + [menuConfig, pathname] ); - // Global classNames for consistent styling const classNames: AccordionMenuClassNames = { root: 'space-y-2.5 px-3.5', group: 'gap-px', @@ -33,8 +35,6 @@ export function SidebarMenuPrimary() { indicator: '' }; - const menuConfig = useMenuConfig(); - const buildMenu = (items: MenuConfig): JSX.Element[] => { const menuItems = getPermittedMenuItems(items, userPermissions); @@ -82,7 +82,7 @@ export function SidebarMenuPrimary() { if (!item.heading && !item.disabled) { return buildMenuItemChild(item, index, level); } else { - return <>; + return ; } }); }; diff --git a/apps/admin/src/app/layouts/06__MultipleMenus/layout.tsx b/apps/admin/src/app/layouts/06__MultipleMenus/layout.tsx index f6a3669..79c2d7f 100755 --- a/apps/admin/src/app/layouts/06__MultipleMenus/layout.tsx +++ b/apps/admin/src/app/layouts/06__MultipleMenus/layout.tsx @@ -1,7 +1,7 @@ // DEMO 6 // https://keenthemes.com/metronic/tailwind/react/demo6 -import React from 'react'; +import { Fragment, useEffect, useMemo } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import { useBodyClass, useIsMobile } from '@crux-kit/hooks'; import { LayoutNames } from '#app/config/types'; @@ -23,14 +23,15 @@ export const MultipleMenusLayout = () => { bg-muted! `); - React.useEffect(() => { + useEffect(() => { setOption('layout', LayoutNames.MULTIPLE_MENUS); }, [setOption]); - const toolbar = React.useMemo(() => { + const toolbar = useMemo(() => { if (pathname.includes('/devtools')) return null; if (pathname.includes('/access-control')) return null; if (pathname.includes('/file-manager')) return null; + if (pathname.includes('/web-studio')) return null; return ( @@ -39,13 +40,13 @@ export const MultipleMenusLayout = () => { ); }, [pathname]); - const footer = React.useMemo(() => { + const footer = useMemo(() => { if (pathname.includes('/file-manager')) return null; return
; }, [pathname]); return ( - +
{!isMobile && } @@ -63,6 +64,6 @@ export const MultipleMenusLayout = () => {
- + ); }; diff --git a/apps/admin/src/app/layouts/08__DropdownMenu/components/SidebarMenu.tsx b/apps/admin/src/app/layouts/08__DropdownMenu/components/SidebarMenu.tsx index 313a888..51b0106 100755 --- a/apps/admin/src/app/layouts/08__DropdownMenu/components/SidebarMenu.tsx +++ b/apps/admin/src/app/layouts/08__DropdownMenu/components/SidebarMenu.tsx @@ -4,12 +4,13 @@ import { cn } from '@crux-kit/utils'; import { MenuConfig } from '#app/config/types'; import { useMenuConfig } from '#app/menu'; import { useMenu } from '#shared/hooks/useMenu'; +import { createMenuPathMatcher } from '#shared/lib/menuPath'; import { Button } from '#shared/ui/Button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '#shared/ui/DropdownMenu'; export function SidebarMenu() { const { pathname } = useLocation(); - const { isActive, hasActiveChild } = useMenu(pathname); + const { hasActiveChild } = useMenu(pathname); const config = useMenuConfig(); const menuConfig: MenuConfig = [ @@ -40,6 +41,9 @@ export function SidebarMenu() { } ]; + // A leaf highlights only when nothing deeper in the menu matches the route more closely. + const isActive = createMenuPathMatcher(menuConfig, pathname); + const buildMenu = (items: MenuConfig) => { return items.map((item, index) => (
diff --git a/apps/admin/src/app/layouts/10__DarkSidebar/components/SidebarMenuPrimary.tsx b/apps/admin/src/app/layouts/10__DarkSidebar/components/SidebarMenuPrimary.tsx index 83ea3b8..30d5325 100755 --- a/apps/admin/src/app/layouts/10__DarkSidebar/components/SidebarMenuPrimary.tsx +++ b/apps/admin/src/app/layouts/10__DarkSidebar/components/SidebarMenuPrimary.tsx @@ -1,10 +1,11 @@ 'use client'; -import { JSX, useCallback } from 'react'; +import { JSX, useMemo } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { cn } from '@crux-kit/utils'; import { MenuConfig, MenuItem } from '#app/config/types'; import { useMenuConfig } from '#app/menu'; +import { createMenuPathMatcher } from '#shared/lib/menuPath'; import { AccordionMenu, AccordionMenuClassNames, AccordionMenuGroup, AccordionMenuItem, AccordionMenuLabel, AccordionMenuSub, AccordionMenuSubContent, AccordionMenuSubTrigger } from '#shared/ui/AccordionMenu'; export function SidebarMenuPrimary() { @@ -12,9 +13,9 @@ export function SidebarMenuPrimary() { const menuConfig = useMenuConfig(); // Memoize matchPath to prevent unnecessary re-renders - const matchPath = useCallback( - (path: string): boolean => path === pathname || (path.length > 1 && pathname.startsWith(path)), - [pathname] + const matchPath = useMemo( + () => createMenuPathMatcher(menuConfig, pathname), + [menuConfig, pathname] ); // Global classNames for consistent styling diff --git a/apps/admin/src/app/menu.tsx b/apps/admin/src/app/menu.tsx index 52866be..6f88411 100755 --- a/apps/admin/src/app/menu.tsx +++ b/apps/admin/src/app/menu.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useMemo } from 'react'; import { useIntl } from 'react-intl'; import { type MenuConfig } from './config/types'; import { createModuleMenuConfig } from './services/modules'; @@ -6,7 +6,7 @@ import { createModuleMenuConfig } from './services/modules'; export const useMenuConfig = (): MenuConfig => { const intl = useIntl(); - return React.useMemo(() => { + return useMemo(() => { return createModuleMenuConfig(intl); }, [intl]); }; diff --git a/apps/admin/src/app/providers/AppProviders.tsx b/apps/admin/src/app/providers/AppProviders.tsx index 8d1f995..1e06ca7 100644 --- a/apps/admin/src/app/providers/AppProviders.tsx +++ b/apps/admin/src/app/providers/AppProviders.tsx @@ -1,4 +1,4 @@ -import { type ReactNode } from 'react'; +import type { ReactNode } from 'react'; import { BrowserRouter } from 'react-router-dom'; import { BASE_URL } from '#app/config/app'; import { AuthProvider } from '#core/Auth/providers/AuthProvider'; diff --git a/apps/admin/src/app/providers/I18nProvider.tsx b/apps/admin/src/app/providers/I18nProvider.tsx index 5c50411..27fb64f 100755 --- a/apps/admin/src/app/providers/I18nProvider.tsx +++ b/apps/admin/src/app/providers/I18nProvider.tsx @@ -1,51 +1,48 @@ -import { DirectionProvider as RadixDirectionProvider } from '@radix-ui/react-direction'; -import { createContext, useContext, useEffect, useState, type PropsWithChildren } from 'react'; +import { DirectionProvider } from '@radix-ui/react-direction'; +import { useEffect, useState, type PropsWithChildren } from 'react'; import { IntlProvider } from 'react-intl'; import { LocalStorageService } from '@crux-kit/local-storage'; import { I18N_DEFAULT_LANGUAGE, I18N_LANGUAGES } from '#app/i18n/config'; -import { I18nProviderProps, type Language } from '#app/i18n/types'; +import TranslationsContext from '#app/i18n/context'; +import { type Language, type LanguageCode } from '#app/i18n/types'; import { IntlDate } from '#infrastructure/IntlDate'; +import { IntlName } from '#infrastructure/IntlName'; import '@formatjs/intl-relativetimeformat/polyfill'; import '@formatjs/intl-relativetimeformat/locale-data/en'; -import '@formatjs/intl-relativetimeformat/locale-data/de'; -import '@formatjs/intl-relativetimeformat/locale-data/es'; -import '@formatjs/intl-relativetimeformat/locale-data/fr'; -import '@formatjs/intl-relativetimeformat/locale-data/ja'; -import '@formatjs/intl-relativetimeformat/locale-data/zh'; const storage = new LocalStorageService({ prefix: 'i18n' }); +const resolveLanguage = (code?: LanguageCode): Language => { + return I18N_LANGUAGES.find((lang) => lang.code === code) ?? I18N_DEFAULT_LANGUAGE; +}; + const getInitialLanguage = () => { const urlParams = new URLSearchParams(window.location.search); const langParam = urlParams.get('lang'); - // Check if langParam matches a supported language in I18N_LANGUAGES if (langParam) { const matchedLanguage = I18N_LANGUAGES.find((lang) => lang.code === langParam); if (matchedLanguage) { - storage.set('config', matchedLanguage); + storage.set('config', matchedLanguage.code); return matchedLanguage; } } - return storage.get('config') ?? I18N_DEFAULT_LANGUAGE; + return resolveLanguage(storage.get('config')); }; -const initialProps: I18nProviderProps = { - languages: I18N_LANGUAGES, - currenLanguage: getInitialLanguage(), - changeLanguage: (_: Language) => {}, - isRTL: () => false -}; +const initialLanguage = getInitialLanguage(); -const TranslationsContext = createContext(initialProps); -const useLanguage = () => useContext(TranslationsContext); +IntlDate.setLocaleGlobally(initialLanguage.code); +IntlName.setLanguage(initialLanguage.code); const I18nProvider = ({ children }: PropsWithChildren) => { - const [currenLanguage, setCurrenLanguage] = useState(initialProps.currenLanguage); + const [currenLanguage, setCurrenLanguage] = useState(initialLanguage); const changeLanguage = (language: Language) => { - storage.set('config', language); + storage.set('config', language.code); + IntlDate.setLocaleGlobally(language.code); + IntlName.setLanguage(language.code); setCurrenLanguage(language); }; @@ -54,7 +51,6 @@ const I18nProvider = ({ children }: PropsWithChildren) => { }; useEffect(() => { - IntlDate.setLocaleGlobally(currenLanguage.code); // TODO: currently changes only after page reload! document.documentElement.setAttribute('dir', currenLanguage.direction); }, [currenLanguage]); @@ -70,13 +66,12 @@ const I18nProvider = ({ children }: PropsWithChildren) => { - {children} + {children} ); }; -export { useLanguage }; export default I18nProvider; diff --git a/apps/admin/src/app/providers/QueryProvider.tsx b/apps/admin/src/app/providers/QueryProvider.tsx index ba427cb..fc00d43 100755 --- a/apps/admin/src/app/providers/QueryProvider.tsx +++ b/apps/admin/src/app/providers/QueryProvider.tsx @@ -1,5 +1,5 @@ import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactNode, useState } from 'react'; +import { useState, type ReactNode } from 'react'; import { handleQueryError } from '#infrastructure/reactQuery'; export const queryClient = new QueryClient({ diff --git a/apps/admin/src/app/providers/SettingsProvider.tsx b/apps/admin/src/app/providers/SettingsProvider.tsx index 2f03ed6..88a4f79 100755 --- a/apps/admin/src/app/providers/SettingsProvider.tsx +++ b/apps/admin/src/app/providers/SettingsProvider.tsx @@ -1,7 +1,7 @@ 'use client'; /* eslint-disable @typescript-eslint/no-explicit-any */ -import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { LocalStorageService } from '@crux-kit/local-storage'; import { APP_SETTINGS } from '#app/config/settings'; import { Settings } from '#app/config/types'; diff --git a/apps/admin/src/app/providers/ThemeProvider.tsx b/apps/admin/src/app/providers/ThemeProvider.tsx index 46a2d88..a7298c5 100755 --- a/apps/admin/src/app/providers/ThemeProvider.tsx +++ b/apps/admin/src/app/providers/ThemeProvider.tsx @@ -1,11 +1,9 @@ 'use client'; import { ThemeProvider as NextThemesProvider } from 'next-themes'; -import * as React from 'react'; +import { type ComponentProps } from 'react'; -export default function ThemeProvider({ - children -}: React.ComponentProps) { +export default function ThemeProvider({ children }: ComponentProps) { return ( >; diff --git a/apps/admin/src/app/services/modules/registry.ts b/apps/admin/src/app/services/modules/registry.ts index 5c44100..27bc1fc 100644 --- a/apps/admin/src/app/services/modules/registry.ts +++ b/apps/admin/src/app/services/modules/registry.ts @@ -1,4 +1,4 @@ -import { AppModuleConfig } from './types'; +import { type AppModuleConfig } from './types'; const coreModules = import.meta.glob('#core/**/config/module.ts', { eager: true, diff --git a/apps/admin/src/app/services/modules/routes.ts b/apps/admin/src/app/services/modules/routes.ts index dbbcde5..ce34f6f 100644 --- a/apps/admin/src/app/services/modules/routes.ts +++ b/apps/admin/src/app/services/modules/routes.ts @@ -1,5 +1,5 @@ import { appModules } from './registry'; -import { AppModuleRouteScope } from './types'; +import { type AppModuleRouteScope } from './types'; export const getModuleRoutes = (scope: AppModuleRouteScope) => { return appModules diff --git a/apps/admin/src/core/AccessControl/config/menu.ts b/apps/admin/src/core/AccessControl/config/menu.ts index 6bc62ea..72ef8d9 100755 --- a/apps/admin/src/core/AccessControl/config/menu.ts +++ b/apps/admin/src/core/AccessControl/config/menu.ts @@ -1,5 +1,5 @@ import { Users } from 'lucide-react'; -import { IntlShape } from 'react-intl'; +import { type IntlShape } from 'react-intl'; import { MenuItem } from '#app/config/types'; export const createAccessControlMenu = (intl: IntlShape): MenuItem => ({ diff --git a/apps/admin/src/core/AccessControl/config/module.ts b/apps/admin/src/core/AccessControl/config/module.ts index 716a2b6..d7d76d1 100644 --- a/apps/admin/src/core/AccessControl/config/module.ts +++ b/apps/admin/src/core/AccessControl/config/module.ts @@ -1,4 +1,4 @@ -import { AppModuleConfig } from '#app/services/modules/types'; +import { type AppModuleConfig } from '#app/services/modules/types'; import { createAccessControlMenu } from './menu'; import { accessControlMessages } from './messages'; import { accessControlRoute } from './routing'; diff --git a/apps/admin/src/core/AccessControl/config/routing.tsx b/apps/admin/src/core/AccessControl/config/routing.tsx index 7176f5b..9004639 100755 --- a/apps/admin/src/core/AccessControl/config/routing.tsx +++ b/apps/admin/src/core/AccessControl/config/routing.tsx @@ -1,4 +1,4 @@ -import { Navigate, RouteObject } from 'react-router-dom'; +import { Navigate, type RouteObject } from 'react-router-dom'; import AdminListPage from '../pages/AdminListPage'; import RoleListPage from '../pages/RoleListPage'; import AdminManagementLayout from '../view/layouts/AdminManagementLayout'; diff --git a/apps/admin/src/core/AccessControl/data/api/dto.ts b/apps/admin/src/core/AccessControl/data/api/AccessControlDTO.ts similarity index 100% rename from apps/admin/src/core/AccessControl/data/api/dto.ts rename to apps/admin/src/core/AccessControl/data/api/AccessControlDTO.ts diff --git a/apps/admin/src/core/AccessControl/data/api/AdminAPI.ts b/apps/admin/src/core/AccessControl/data/api/AdminAPI.ts index e3fa9c0..84017a1 100644 --- a/apps/admin/src/core/AccessControl/data/api/AdminAPI.ts +++ b/apps/admin/src/core/AccessControl/data/api/AdminAPI.ts @@ -1,6 +1,6 @@ import { axiosClient } from '#infrastructure/axios'; -import { ListQueryParams, SuccessListResponse, SuccessMutationResponse } from '#infrastructure/reactQuery'; -import { AdminDTO } from './dto'; +import type { ListQueryParams, SuccessListResponse, SuccessMutationResponse } from '#infrastructure/reactQuery'; +import type { AdminDTO } from './AccessControlDTO'; const ROOT_API_ENDPOINT = '/admin'; diff --git a/apps/admin/src/core/AccessControl/data/api/PermissionAPI.ts b/apps/admin/src/core/AccessControl/data/api/PermissionAPI.ts index 1ea5ce1..789f0c2 100644 --- a/apps/admin/src/core/AccessControl/data/api/PermissionAPI.ts +++ b/apps/admin/src/core/AccessControl/data/api/PermissionAPI.ts @@ -1,5 +1,5 @@ import { axiosClient } from '#infrastructure/axios'; -import { PermissionDTO } from './dto'; +import type { PermissionDTO } from './AccessControlDTO'; const ROOT_API_ENDPOINT = '/permission'; diff --git a/apps/admin/src/core/AccessControl/data/api/RoleAPI.ts b/apps/admin/src/core/AccessControl/data/api/RoleAPI.ts index f0c39c2..4b2580e 100644 --- a/apps/admin/src/core/AccessControl/data/api/RoleAPI.ts +++ b/apps/admin/src/core/AccessControl/data/api/RoleAPI.ts @@ -1,6 +1,6 @@ import { axiosClient } from '#infrastructure/axios'; -import { ListQueryParams, SuccessListResponse, SuccessMutationResponse } from '#infrastructure/reactQuery'; -import { RoleDTO } from './dto'; +import type { ListQueryParams, SuccessListResponse, SuccessMutationResponse } from '#infrastructure/reactQuery'; +import type { RoleDTO } from './AccessControlDTO'; const ROOT_API_ENDPOINT = '/role'; diff --git a/apps/admin/src/core/AccessControl/data/mappers/AdminMapper.ts b/apps/admin/src/core/AccessControl/data/mappers/AdminMapper.ts index 74d1858..e98b163 100644 --- a/apps/admin/src/core/AccessControl/data/mappers/AdminMapper.ts +++ b/apps/admin/src/core/AccessControl/data/mappers/AdminMapper.ts @@ -1,6 +1,6 @@ -import { AdminEntity, AdminStatusEnum } from '../../entities/AdminEntity'; -import { AdminDTO } from '../api/dto'; -import RoleMapper from './RoleMapper'; +import type { AdminDTO } from '#core/AccessControl/data/api/AccessControlDTO'; +import RoleMapper from '#core/AccessControl/data/mappers/RoleMapper'; +import type { AdminEntity, AdminStatusEnum } from '#core/AccessControl/entities/AdminEntity'; export default class AdminMapper { private readonly roleMapper = new RoleMapper(); diff --git a/apps/admin/src/core/AccessControl/data/mappers/PermissionMapper.ts b/apps/admin/src/core/AccessControl/data/mappers/PermissionMapper.ts index da604b2..340ae78 100644 --- a/apps/admin/src/core/AccessControl/data/mappers/PermissionMapper.ts +++ b/apps/admin/src/core/AccessControl/data/mappers/PermissionMapper.ts @@ -1,5 +1,5 @@ -import { PermissionEntity } from '../../entities/PermissionEntity'; -import { PermissionDTO } from '../api/dto'; +import type { PermissionDTO } from '#core/AccessControl/data/api/AccessControlDTO'; +import type { PermissionEntity } from '#core/AccessControl/entities/PermissionEntity'; export default class PermissionMapper { public toEntity = (dto: PermissionDTO): PermissionEntity => { diff --git a/apps/admin/src/core/AccessControl/data/mappers/RoleMapper.ts b/apps/admin/src/core/AccessControl/data/mappers/RoleMapper.ts index 6d88b97..b567aec 100644 --- a/apps/admin/src/core/AccessControl/data/mappers/RoleMapper.ts +++ b/apps/admin/src/core/AccessControl/data/mappers/RoleMapper.ts @@ -1,6 +1,6 @@ -import { RoleEntity } from '../../entities/RoleEntity'; -import { RoleDTO } from '../api/dto'; -import PermissionMapper from './PermissionMapper'; +import type { RoleDTO } from '#core/AccessControl/data/api/AccessControlDTO'; +import PermissionMapper from '#core/AccessControl/data/mappers/PermissionMapper'; +import type { RoleEntity } from '#core/AccessControl/entities/RoleEntity'; export default class RoleMapper { private readonly permissionMapper = new PermissionMapper(); diff --git a/apps/admin/src/core/AccessControl/data/queries/admin.ts b/apps/admin/src/core/AccessControl/data/queries/admin.ts index 7a62b55..0745d82 100644 --- a/apps/admin/src/core/AccessControl/data/queries/admin.ts +++ b/apps/admin/src/core/AccessControl/data/queries/admin.ts @@ -1,10 +1,10 @@ import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'; -import React from 'react'; +import { useMemo } from 'react'; import { toast } from 'sonner'; -import { ErrorResponse, getPaginationInfo, ListQueryParams, SuccessMutationResponse } from '#infrastructure/reactQuery'; -import AdminAPI from '../api/AdminAPI'; -import AdminMapper from '../mappers/AdminMapper'; -import { adminDetailedKeyBuilder, adminListKeyBuilder, DetailedPayload } from './keys'; +import { getPaginationInfo, type ErrorResponse, type ListQueryParams, type SuccessMutationResponse } from '#infrastructure/reactQuery'; +import AdminAPI from '#core/AccessControl/data/api/AdminAPI'; +import AdminMapper from '#core/AccessControl/data/mappers/AdminMapper'; +import { adminDetailedKeyBuilder, adminListKeyBuilder, type DetailedPayload } from './keys'; const api = new AdminAPI(); const mapper = new AdminMapper(); @@ -19,7 +19,7 @@ export const useGetList = (params: ListQueryParams = { page: 1, perPage: 10 }) = } }); - const list = React.useMemo(() => { + const list = useMemo(() => { return mapper.toEntities(data?.data ?? []); }, [data?.data]); diff --git a/apps/admin/src/core/AccessControl/data/queries/keys.ts b/apps/admin/src/core/AccessControl/data/queries/keys.ts index 77dd227..7e04957 100644 --- a/apps/admin/src/core/AccessControl/data/queries/keys.ts +++ b/apps/admin/src/core/AccessControl/data/queries/keys.ts @@ -1,4 +1,4 @@ -import { createKeyBuilder, ListQueryParams } from '#infrastructure/reactQuery'; +import { createKeyBuilder, type ListQueryParams } from '#infrastructure/reactQuery'; export interface DetailedPayload { id: string; diff --git a/apps/admin/src/core/AccessControl/data/queries/permission.ts b/apps/admin/src/core/AccessControl/data/queries/permission.ts index f4fd340..bcbd762 100644 --- a/apps/admin/src/core/AccessControl/data/queries/permission.ts +++ b/apps/admin/src/core/AccessControl/data/queries/permission.ts @@ -1,6 +1,6 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'; -import PermissionAPI from '../api/PermissionAPI'; -import PermissionMapper from '../mappers/PermissionMapper'; +import PermissionAPI from '#core/AccessControl/data/api/PermissionAPI'; +import PermissionMapper from '#core/AccessControl/data/mappers/PermissionMapper'; import { permissionListKeyBuilder } from './keys'; const api = new PermissionAPI(); diff --git a/apps/admin/src/core/AccessControl/data/queries/role.ts b/apps/admin/src/core/AccessControl/data/queries/role.ts index 4e223c0..58919ef 100644 --- a/apps/admin/src/core/AccessControl/data/queries/role.ts +++ b/apps/admin/src/core/AccessControl/data/queries/role.ts @@ -1,10 +1,10 @@ import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'; import React from 'react'; import { toast } from 'sonner'; -import { ErrorResponse, getPaginationInfo, ListQueryParams, SuccessMutationResponse } from '#infrastructure/reactQuery'; -import RoleAPI from '../api/RoleAPI'; -import RoleMapper from '../mappers/RoleMapper'; -import { DetailedPayload, roleDetailedKeyBuilder, roleListKeyBuilder } from './keys'; +import { getPaginationInfo, type ErrorResponse, type ListQueryParams, type SuccessMutationResponse } from '#infrastructure/reactQuery'; +import RoleAPI from '#core/AccessControl/data/api/RoleAPI'; +import RoleMapper from '#core/AccessControl/data/mappers/RoleMapper'; +import { roleDetailedKeyBuilder, roleListKeyBuilder, type DetailedPayload } from './keys'; const api = new RoleAPI(); const mapper = new RoleMapper(); diff --git a/apps/admin/src/core/AccessControl/entities/AdminEntity.ts b/apps/admin/src/core/AccessControl/entities/AdminEntity.ts index a2fc997..69f4e1e 100644 --- a/apps/admin/src/core/AccessControl/entities/AdminEntity.ts +++ b/apps/admin/src/core/AccessControl/entities/AdminEntity.ts @@ -1,5 +1,5 @@ import type { IsoDateString } from '@crux-kit/types'; -import { RoleEntity } from './RoleEntity'; +import type { RoleEntity } from './RoleEntity'; export enum AdminStatusEnum { ACTIVE = 'active', diff --git a/apps/admin/src/core/AccessControl/entities/RoleEntity.ts b/apps/admin/src/core/AccessControl/entities/RoleEntity.ts index 7e611e1..3960604 100644 --- a/apps/admin/src/core/AccessControl/entities/RoleEntity.ts +++ b/apps/admin/src/core/AccessControl/entities/RoleEntity.ts @@ -1,5 +1,5 @@ import type { IsoDateString } from '@crux-kit/types'; -import { PermissionEntity } from './PermissionEntity'; +import type { PermissionEntity } from './PermissionEntity'; export interface RoleEntity { id: number; diff --git a/apps/admin/src/core/AccessControl/pages/AdminListPage/index.tsx b/apps/admin/src/core/AccessControl/pages/AdminListPage/index.tsx index a731769..513d207 100755 --- a/apps/admin/src/core/AccessControl/pages/AdminListPage/index.tsx +++ b/apps/admin/src/core/AccessControl/pages/AdminListPage/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { AdminTable } from '#core/AccessControl/view/widgets/AdminTable'; import { PermissionPageGate } from '#core/Permission'; import { Container } from '#shared/widgets/common/Container'; -import { AdminTable } from '../../view/widgets/AdminTable'; const AdminListPage: React.FC = () => { return ( diff --git a/apps/admin/src/core/AccessControl/pages/RoleListPage/index.tsx b/apps/admin/src/core/AccessControl/pages/RoleListPage/index.tsx index 81cd830..d4e6b66 100755 --- a/apps/admin/src/core/AccessControl/pages/RoleListPage/index.tsx +++ b/apps/admin/src/core/AccessControl/pages/RoleListPage/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { RoleTable } from '#core/AccessControl/view/widgets/RoleTable'; import { PermissionPageGate } from '#core/Permission'; import { Container } from '#shared/widgets/common/Container'; -import { RoleTable } from '../../view/widgets/RoleTable'; const RoleListPage: React.FC = () => { return ( diff --git a/apps/admin/src/core/AccessControl/view/forms/AdminForm/Component.tsx b/apps/admin/src/core/AccessControl/view/forms/AdminForm/Component.tsx index 027b578..a3fc164 100755 --- a/apps/admin/src/core/AccessControl/view/forms/AdminForm/Component.tsx +++ b/apps/admin/src/core/AccessControl/view/forms/AdminForm/Component.tsx @@ -9,7 +9,7 @@ import { FormElement, FormGrid, GridElement, InputCheckboxGroup, InputSelect, In import { Button } from '#shared/ui/Button'; import { Form as FormProvider } from '#shared/ui/form'; import { Separator } from '#shared/ui/Separator'; -import { ADMIN_FORM_ID, createValidationSchema, FormValues, SubmitFunc } from './form'; +import { ADMIN_FORM_ID, createValidationSchema, type FormValues, type SubmitFunc } from './form'; export const AdminForm: React.FC<{ roles: Array<{ label: string; value: string }>; diff --git a/apps/admin/src/core/AccessControl/view/forms/AdminForm/form.ts b/apps/admin/src/core/AccessControl/view/forms/AdminForm/form.ts index f6343fd..ce8b6bb 100755 --- a/apps/admin/src/core/AccessControl/view/forms/AdminForm/form.ts +++ b/apps/admin/src/core/AccessControl/view/forms/AdminForm/form.ts @@ -1,8 +1,8 @@ -import { UseFormReturn } from 'react-hook-form'; -import { IntlShape } from 'react-intl'; +import { type UseFormReturn } from 'react-hook-form'; +import { type IntlShape } from 'react-intl'; import z from 'zod'; import { uid } from '@crux-kit/utils'; -import { AdminEntity, AdminStatusEnum } from '#core/AccessControl/entities/AdminEntity'; +import { AdminStatusEnum, type AdminEntity } from '#core/AccessControl/entities/AdminEntity'; export const ADMIN_FORM_ID = uid(); diff --git a/apps/admin/src/core/AccessControl/view/forms/AdminForm/submit.ts b/apps/admin/src/core/AccessControl/view/forms/AdminForm/submit.ts index 4c4da68..529477d 100755 --- a/apps/admin/src/core/AccessControl/view/forms/AdminForm/submit.ts +++ b/apps/admin/src/core/AccessControl/view/forms/AdminForm/submit.ts @@ -2,11 +2,12 @@ import { extractValidationErrors } from '#infrastructure/reactQuery'; import { useCreate, useUpdate } from '#core/AccessControl/data/queries/admin'; import { invalidateAdminDetailed, invalidateAdminList } from '#core/AccessControl/data/queries/cache'; import { useAuth } from '#core/Auth/providers/AuthProvider'; -import { SubmitFunc } from './form'; +import { type SubmitFunc } from './form'; interface Params { onSuccess?: (id?: number) => void; } + export const useSubmit = (params?: Params) => { const onSuccess = params?.onSuccess; const { verify } = useAuth(); diff --git a/apps/admin/src/core/AccessControl/view/forms/RoleForm/Component.tsx b/apps/admin/src/core/AccessControl/view/forms/RoleForm/Component.tsx index 60379da..af26e37 100755 --- a/apps/admin/src/core/AccessControl/view/forms/RoleForm/Component.tsx +++ b/apps/admin/src/core/AccessControl/view/forms/RoleForm/Component.tsx @@ -5,7 +5,7 @@ import { useIntl } from 'react-intl'; import { FormElement, FormGrid, GridElement, InputText } from '#shared/forms/composed'; import { FormControl, FormField, FormItem, FormLabel, FormMessage, Form as FormProvider } from '#shared/ui/form'; import { Switch, SwitchWrapper } from '#shared/ui/Switch'; -import { createValidationSchema, FormValues, ROLE_FORM_ID, SubmitFunc } from './form'; +import { createValidationSchema, ROLE_FORM_ID, type FormValues, type SubmitFunc } from './form'; export const RoleForm: React.FC<{ permissions: Record>; diff --git a/apps/admin/src/core/AccessControl/view/forms/RoleForm/form.ts b/apps/admin/src/core/AccessControl/view/forms/RoleForm/form.ts index 1b81d9b..79614b8 100755 --- a/apps/admin/src/core/AccessControl/view/forms/RoleForm/form.ts +++ b/apps/admin/src/core/AccessControl/view/forms/RoleForm/form.ts @@ -2,7 +2,7 @@ import { UseFormReturn } from 'react-hook-form'; import { IntlShape } from 'react-intl'; import z from 'zod'; import { uid } from '@crux-kit/utils'; -import { RoleEntity } from '#core/AccessControl/entities/RoleEntity'; +import { type RoleEntity } from '#core/AccessControl/entities/RoleEntity'; export const ROLE_FORM_ID = uid(); diff --git a/apps/admin/src/core/AccessControl/view/forms/RoleForm/submit.ts b/apps/admin/src/core/AccessControl/view/forms/RoleForm/submit.ts index a3124d1..16612db 100755 --- a/apps/admin/src/core/AccessControl/view/forms/RoleForm/submit.ts +++ b/apps/admin/src/core/AccessControl/view/forms/RoleForm/submit.ts @@ -1,10 +1,10 @@ -import { ErrorOption, UseFormReturn } from 'react-hook-form'; +import type { ErrorOption, UseFormReturn } from 'react-hook-form'; import { toast } from 'sonner'; -import { ErrorResponse, extractValidationErrors } from '#infrastructure/reactQuery'; +import { extractValidationErrors, type ErrorResponse } from '#infrastructure/reactQuery'; import { invalidateRoleDetailed, invalidateRoleList } from '#core/AccessControl/data/queries/cache'; import { useCreate, useUpdate } from '#core/AccessControl/data/queries/role'; import { useAuth } from '#core/Auth/providers/AuthProvider'; -import { FormValues, SubmitFunc } from './form'; +import type { FormValues, SubmitFunc } from './form'; interface Params { onSuccess?: (id?: number) => void; diff --git a/apps/admin/src/core/AccessControl/view/layouts/AdminManagementLayout.tsx b/apps/admin/src/core/AccessControl/view/layouts/AdminManagementLayout.tsx index 86a4367..251307c 100755 --- a/apps/admin/src/core/AccessControl/view/layouts/AdminManagementLayout.tsx +++ b/apps/admin/src/core/AccessControl/view/layouts/AdminManagementLayout.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { Outlet, useLocation } from 'react-router'; +import { createAccessControlMenu } from '#core/AccessControl/config/menu'; +import AccessControlLogo from '#core/AccessControl/view/ui/AccessControlLogo'; import { useMenu } from '#shared/hooks/useMenu'; import { Container } from '#shared/widgets/common/Container'; -import { createAccessControlMenu } from '../../config/menu'; -import AccessControlLogo from '../ui/AccessControlLogo'; const AdminManagementLayout: React.FC = () => { const intl = useIntl(); diff --git a/apps/admin/src/core/AccessControl/view/widgets/AdminTable.tsx b/apps/admin/src/core/AccessControl/view/widgets/AdminTable.tsx index 0f11231..6bf7b40 100755 --- a/apps/admin/src/core/AccessControl/view/widgets/AdminTable.tsx +++ b/apps/admin/src/core/AccessControl/view/widgets/AdminTable.tsx @@ -1,9 +1,10 @@ -import { Column, ColumnDef, ColumnFiltersState, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, PaginationState, SortingState, useReactTable } from '@tanstack/react-table'; +import { getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'; +import type { Column, ColumnDef, ColumnFiltersState, PaginationState, SortingState } from '@tanstack/react-table'; import { LoaderCircleIcon, SearchIcon, Settings2, SquarePen } from 'lucide-react'; import React from 'react'; -import { IntlShape, useIntl } from 'react-intl'; +import { useIntl, type IntlShape } from 'react-intl'; import { useDebounceSearch } from '@crux-kit/hooks'; -import { IntlName } from '#infrastructure/IntlName'; +import useIntlName from '#app/i18n/useIntlName'; import { mapTableStateToListQueryParams } from '#infrastructure/reactQuery'; import { PermissionGate } from '#core/Permission'; import { Badge } from '#shared/ui/Badge'; @@ -15,7 +16,7 @@ import { ScrollArea, ScrollBar } from '#shared/ui/ScrollArea'; import { Skeleton } from '#shared/ui/Skeleton'; import { DataGrid, DataGridColumnFilter, DataGridColumnHeader, DataGridColumnVisibility, DataGridPagination, DataGridTable, DataGridToolbarFilters, useDataGrid } from '#shared/widgets/DataGrid'; import { useGetList } from '../../data/queries/admin'; -import { AdminEntity, AdminStatusEnum } from '../../entities/AdminEntity'; +import { AdminStatusEnum, type AdminEntity } from '../../entities/AdminEntity'; import { DeleteAdminTrigger } from './DeleteAdminTrigger'; import { ManageAdminTrigger } from './ManageAdminTrigger'; @@ -36,6 +37,7 @@ const getStatusOptions = (intl: IntlShape) => [ export const AdminTable = () => { const intl = useIntl(); + const formatName = useIntlName(); const [pagination, setPagination] = React.useState({ pageIndex: 0, @@ -180,7 +182,7 @@ export const AdminTable = () => { cell: (info) => { const data = info.row.original; const id = data.id; - const name = IntlName.formatToDisplay(data, 'SURNAME_NAME'); + const name = formatName(data, 'SURNAME_NAME'); const editModalTitle = intl.formatMessage({ id: 'ACCESS_CONTROL.ADMIN.EDIT' }) + ' ' + name; @@ -207,7 +209,7 @@ export const AdminTable = () => { } } ], - [intl, statusOptions] + [intl, statusOptions, formatName] ); const table = useReactTable({ diff --git a/apps/admin/src/core/AccessControl/view/widgets/ManageAdminTrigger.tsx b/apps/admin/src/core/AccessControl/view/widgets/ManageAdminTrigger.tsx index 4cb618e..a7f0f97 100755 --- a/apps/admin/src/core/AccessControl/view/widgets/ManageAdminTrigger.tsx +++ b/apps/admin/src/core/AccessControl/view/widgets/ManageAdminTrigger.tsx @@ -2,12 +2,12 @@ import { LoaderCircleIcon } from 'lucide-react'; import React from 'react'; import { useIntl } from 'react-intl'; import { useDisclosure } from '@crux-kit/hooks'; +import { useGetDetailed } from '#core/AccessControl/data/queries/admin'; +import { useGetList } from '#core/AccessControl/data/queries/role'; +import { ADMIN_FORM_ID, AdminForm, initialValues, prepareInitialValues, useSubmit } from '#core/AccessControl/view/forms/AdminForm'; import { Button } from '#shared/ui/Button'; import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '#shared/ui/Dialog'; import { Skeleton } from '#shared/ui/Skeleton'; -import { useGetDetailed } from '../../data/queries/admin'; -import { useGetList } from '../../data/queries/role'; -import { ADMIN_FORM_ID, AdminForm, initialValues, prepareInitialValues, useSubmit } from '../forms/AdminForm'; export const ManageAdminTrigger: React.FC< React.PropsWithChildren<{ diff --git a/apps/admin/src/core/AccessControl/view/widgets/ManageRoleTrigger.tsx b/apps/admin/src/core/AccessControl/view/widgets/ManageRoleTrigger.tsx index c6f3c8a..99b5704 100755 --- a/apps/admin/src/core/AccessControl/view/widgets/ManageRoleTrigger.tsx +++ b/apps/admin/src/core/AccessControl/view/widgets/ManageRoleTrigger.tsx @@ -2,12 +2,12 @@ import { LoaderCircleIcon } from 'lucide-react'; import React from 'react'; import { useIntl } from 'react-intl'; import { useDisclosure } from '@crux-kit/hooks'; +import { useGetList } from '#core/AccessControl/data/queries/permission'; +import { useGetDetailed } from '#core/AccessControl/data/queries/role'; +import { initialValues, prepareInitialValues, ROLE_FORM_ID, RoleForm, useSubmit } from '#core/AccessControl/view/forms/RoleForm'; import { Button } from '#shared/ui/Button'; import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '#shared/ui/Dialog'; import { Skeleton } from '#shared/ui/Skeleton'; -import { useGetList } from '../../data/queries/permission'; -import { useGetDetailed } from '../../data/queries/role'; -import { initialValues, prepareInitialValues, ROLE_FORM_ID, RoleForm, useSubmit } from '../forms/RoleForm'; export const ManageRoleTrigger: React.FC< React.PropsWithChildren<{ diff --git a/apps/admin/src/core/AccessControl/view/widgets/RoleTable.tsx b/apps/admin/src/core/AccessControl/view/widgets/RoleTable.tsx index 9011b3c..afa82d0 100755 --- a/apps/admin/src/core/AccessControl/view/widgets/RoleTable.tsx +++ b/apps/admin/src/core/AccessControl/view/widgets/RoleTable.tsx @@ -1,9 +1,14 @@ -import { Column, ColumnDef, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, PaginationState, SortingState, useReactTable } from '@tanstack/react-table'; +import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'; +import type { Column, ColumnDef, PaginationState, SortingState } from '@tanstack/react-table'; import { LoaderCircleIcon, SearchIcon, Settings2, SquarePen } from 'lucide-react'; import React from 'react'; import { useIntl } from 'react-intl'; import { useDebounceSearch } from '@crux-kit/hooks'; import { mapTableStateToListQueryParams } from '#infrastructure/reactQuery'; +import { useGetList } from '#core/AccessControl/data/queries/role'; +import { RoleEntity } from '#core/AccessControl/entities/RoleEntity'; +import { DeleteRoleTrigger } from '#core/AccessControl/view/widgets/DeleteRoleTrigger'; +import { ManageRoleTrigger } from '#core/AccessControl/view/widgets/ManageRoleTrigger'; import { PermissionGate } from '#core/Permission'; import { Button } from '#shared/ui/Button'; import { Card, CardFooter, CardHeader, CardTable, CardTitle, CardToolbar } from '#shared/ui/Card'; @@ -12,10 +17,6 @@ import { Input, InputWrapper } from '#shared/ui/Input'; import { ScrollArea, ScrollBar } from '#shared/ui/ScrollArea'; import { Skeleton } from '#shared/ui/Skeleton'; import { DataGrid, DataGridColumnHeader, DataGridColumnVisibility, DataGridPagination, DataGridTable, useDataGrid } from '#shared/widgets/DataGrid'; -import { useGetList } from '../../data/queries/role'; -import { RoleEntity } from '../../entities/RoleEntity'; -import { DeleteRoleTrigger } from './DeleteRoleTrigger'; -import { ManageRoleTrigger } from './ManageRoleTrigger'; interface IColumnFilterProps { column: Column; diff --git a/apps/admin/src/core/AccountSettings/config/module.ts b/apps/admin/src/core/AccountSettings/config/module.ts index 411e868..e329b84 100644 --- a/apps/admin/src/core/AccountSettings/config/module.ts +++ b/apps/admin/src/core/AccountSettings/config/module.ts @@ -1,4 +1,4 @@ -import { AppModuleConfig } from '#app/services/modules/types'; +import type { AppModuleConfig } from '#app/services/modules/types'; import { accountSettingsMessages } from './messages'; import { accountSettingsRoute } from './routing'; diff --git a/apps/admin/src/core/AccountSettings/config/routing.tsx b/apps/admin/src/core/AccountSettings/config/routing.tsx index 68d7dd5..fe50536 100755 --- a/apps/admin/src/core/AccountSettings/config/routing.tsx +++ b/apps/admin/src/core/AccountSettings/config/routing.tsx @@ -1,6 +1,6 @@ -import { Navigate, Route, RouteObject, Routes } from 'react-router-dom'; -import ProfilePage from '../pages/Profile'; -import AccountLayout from '../view/layouts/AccountLayout'; +import { Navigate, Route, Routes, type RouteObject } from 'react-router-dom'; +import ProfilePage from '#core/AccountSettings/pages/Profile'; +import AccountLayout from '#core/AccountSettings/view/layouts/AccountLayout'; function Routing() { return ( diff --git a/apps/admin/src/core/AccountSettings/pages/Profile/index.tsx b/apps/admin/src/core/AccountSettings/pages/Profile/index.tsx index 96ae9ea..0fc375e 100755 --- a/apps/admin/src/core/AccountSettings/pages/Profile/index.tsx +++ b/apps/admin/src/core/AccountSettings/pages/Profile/index.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { toast } from 'sonner'; import { Toolbar, ToolbarHeading } from '#app/layouts/01__Default/components/Toolbar'; -import { ErrorResponse, extractValidationErrors, isValidationError } from '#infrastructure/reactQuery'; +import { extractValidationErrors, isValidationError, type ErrorResponse } from '#infrastructure/reactQuery'; +import { prepareFormValues, ProfileForm, SubmitFunc } from '#core/AccountSettings/view/forms/ProfileForm'; import { useAuth } from '#core/Auth/providers/AuthProvider'; import { Card, CardContent, CardHeader, CardTitle } from '#shared/ui/Card'; import { Container } from '#shared/widgets/common/Container'; -import { prepareFormValues, ProfileForm, SubmitFunc } from '../../view/forms/ProfileForm'; const ProfilePage: React.FC = () => { const intl = useIntl(); diff --git a/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/Component.tsx b/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/Component.tsx index 5a984ea..00ab38d 100755 --- a/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/Component.tsx +++ b/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/Component.tsx @@ -8,7 +8,7 @@ import { Button } from '#shared/ui/Button'; import { Form as FormProvider } from '#shared/ui/form'; import AvatarCropperField from './fields/AvatarCropperField'; import PasswordField from './fields/PasswordField'; -import { createValidationSchema, FormValues, SubmitFunc } from './form'; +import { createValidationSchema, type FormValues, type SubmitFunc } from './form'; export const ProfileForm: React.FC<{ initialValues: FormValues; diff --git a/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/form.ts b/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/form.ts index c3b9a51..c6661aa 100755 --- a/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/form.ts +++ b/apps/admin/src/core/AccountSettings/view/forms/ProfileForm/form.ts @@ -1,7 +1,7 @@ -import { UseFormReturn } from 'react-hook-form'; -import { IntlShape } from 'react-intl'; +import type { UseFormReturn } from 'react-hook-form'; +import type { IntlShape } from 'react-intl'; import z from 'zod'; -import { AuthUser } from '#core/Auth/entities'; +import type { AuthUser } from '#core/Auth/entities'; export const createValidationSchema = (intl: IntlShape) => { return z diff --git a/apps/admin/src/core/Auth/config/module.ts b/apps/admin/src/core/Auth/config/module.ts index 055d0ec..e14bc1f 100644 --- a/apps/admin/src/core/Auth/config/module.ts +++ b/apps/admin/src/core/Auth/config/module.ts @@ -1,4 +1,4 @@ -import { AppModuleConfig } from '#app/services/modules/types'; +import type { AppModuleConfig } from '#app/services/modules/types'; import { authMessages } from './messages'; import { authRoute } from './routing'; diff --git a/apps/admin/src/core/Auth/config/routing.tsx b/apps/admin/src/core/Auth/config/routing.tsx index b31b46b..3c2d1b8 100755 --- a/apps/admin/src/core/Auth/config/routing.tsx +++ b/apps/admin/src/core/Auth/config/routing.tsx @@ -1,6 +1,6 @@ -import { Navigate, Route, RouteObject, Routes } from 'react-router-dom'; -import { SignInPage } from '../pages/SignInPage'; -import { BrandedLayout } from '../view/layouts/Branded'; +import { Navigate, Route, Routes, type RouteObject } from 'react-router-dom'; +import SignInPage from '#core/Auth/pages/SignInPage'; +import BrandedLayout from '#core/Auth/view/layouts/Branded'; /** * Handles all authentication related routes. diff --git a/apps/admin/src/core/Auth/data/api/api.ts b/apps/admin/src/core/Auth/data/api/api.ts index 7daa9c0..71ff21d 100755 --- a/apps/admin/src/core/Auth/data/api/api.ts +++ b/apps/admin/src/core/Auth/data/api/api.ts @@ -1,6 +1,6 @@ import { axiosClient } from '#infrastructure/axios'; -import { AuthModel } from '#core/Auth/entities'; -import { GetUserResponse, UpdateUserPayload } from './dto'; +import type { AuthModel } from '#core/Auth/entities'; +import type { GetUserResponse, UpdateUserPayload } from './dto'; export const AuthApi = { async login(email: string, password: string): Promise { diff --git a/apps/admin/src/core/Auth/data/api/dto.ts b/apps/admin/src/core/Auth/data/api/dto.ts index f7296b6..d4aa33b 100644 --- a/apps/admin/src/core/Auth/data/api/dto.ts +++ b/apps/admin/src/core/Auth/data/api/dto.ts @@ -1,4 +1,4 @@ -import { AuthUser } from '#core/Auth/entities'; +import type { AuthUser } from '#core/Auth/entities'; export type UpdateUserPayload = Partial; diff --git a/apps/admin/src/core/Auth/data/mappers/UserMapper.ts b/apps/admin/src/core/Auth/data/mappers/UserMapper.ts index 90ea3bc..cc295b1 100644 --- a/apps/admin/src/core/Auth/data/mappers/UserMapper.ts +++ b/apps/admin/src/core/Auth/data/mappers/UserMapper.ts @@ -1,5 +1,5 @@ -import { AuthUser } from '../../entities'; -import { GetUserResponse } from '../api/dto'; +import type { GetUserResponse } from '#core/Auth/data/api/dto'; +import type { AuthUser } from '#core/Auth/entities'; export default class UserMapper { constructor() {} diff --git a/apps/admin/src/core/Auth/data/stores/localStorage.ts b/apps/admin/src/core/Auth/data/stores/localStorage.ts index 57582a3..135642c 100755 --- a/apps/admin/src/core/Auth/data/stores/localStorage.ts +++ b/apps/admin/src/core/Auth/data/stores/localStorage.ts @@ -1,6 +1,6 @@ import { LocalStorageService } from '@crux-kit/local-storage'; import { APP_VERSION } from '#app/config/app'; -import { AuthModel } from '#core/Auth/entities'; +import type { AuthModel } from '#core/Auth/entities'; const storage = new LocalStorageService({ prefix: `auth-v${APP_VERSION || '1.0'}` diff --git a/apps/admin/src/core/Auth/entities/index.ts b/apps/admin/src/core/Auth/entities/index.ts index a0032eb..0863092 100644 --- a/apps/admin/src/core/Auth/entities/index.ts +++ b/apps/admin/src/core/Auth/entities/index.ts @@ -1,2 +1,2 @@ -export * from './auth'; -export * from './user'; +export type { AuthModel } from './auth'; +export type { AuthUser } from './user'; diff --git a/apps/admin/src/core/Auth/pages/SignInPage.tsx b/apps/admin/src/core/Auth/pages/SignInPage.tsx index 711c2e4..2f1ae44 100755 --- a/apps/admin/src/core/Auth/pages/SignInPage.tsx +++ b/apps/admin/src/core/Auth/pages/SignInPage.tsx @@ -7,7 +7,7 @@ import { useAuth } from '../providers/AuthProvider'; import { SignInForm } from '../view/forms/SignInForm/Component'; import { SubmitFunc } from '../view/forms/SignInForm/form'; -export function SignInPage() { +export default function SignInPage() { const { login } = useAuth(); const intl = useIntl(); diff --git a/apps/admin/src/core/Auth/providers/AuthProvider/AuthProvider.tsx b/apps/admin/src/core/Auth/providers/AuthProvider/AuthProvider.tsx index a1bb45b..8212515 100755 --- a/apps/admin/src/core/Auth/providers/AuthProvider/AuthProvider.tsx +++ b/apps/admin/src/core/Auth/providers/AuthProvider/AuthProvider.tsx @@ -1,15 +1,15 @@ -import { PropsWithChildren, useState } from 'react'; +import { useState, type PropsWithChildren } from 'react'; import { queryClient } from '#app/providers/QueryProvider'; import { AuthApi } from '#core/Auth/data/api/api'; -import { UpdateUserPayload } from '#core/Auth/data/api/dto'; +import type { UpdateUserPayload } from '#core/Auth/data/api/dto'; import UserMapper from '#core/Auth/data/mappers/UserMapper'; -import * as authHelper from '#core/Auth/data/stores/localStorage'; -import { AuthModel, AuthUser } from '#core/Auth/entities'; +import { getAuth, removeAuth, setAuth as setStoredAuth } from '#core/Auth/data/stores/localStorage'; +import type { AuthModel, AuthUser } from '#core/Auth/entities'; import { AuthContext } from './context'; export function AuthProvider({ children }: PropsWithChildren) { const [loading, setLoading] = useState(true); - const [auth, setAuth] = useState(authHelper.getAuth()); + const [auth, setAuth] = useState(getAuth()); const [currentUser, setCurrentUser] = useState(); const verify = async () => { @@ -29,9 +29,9 @@ export function AuthProvider({ children }: PropsWithChildren) { const saveAuth = (auth: AuthModel | undefined) => { setAuth(auth); if (auth) { - authHelper.setAuth(auth); + setStoredAuth(auth); } else { - authHelper.removeAuth(); + removeAuth(); } }; diff --git a/apps/admin/src/core/Auth/providers/AuthProvider/context.ts b/apps/admin/src/core/Auth/providers/AuthProvider/context.ts index 14801df..f4ce921 100755 --- a/apps/admin/src/core/Auth/providers/AuthProvider/context.ts +++ b/apps/admin/src/core/Auth/providers/AuthProvider/context.ts @@ -1,6 +1,6 @@ import { createContext, useContext } from 'react'; -import { GetUserResponse, UpdateUserPayload } from '#core/Auth/data/api/dto'; -import { AuthModel, AuthUser } from '#core/Auth/entities'; +import type { GetUserResponse, UpdateUserPayload } from '#core/Auth/data/api/dto'; +import type { AuthModel, AuthUser } from '#core/Auth/entities'; export const AuthContext = createContext<{ loading: boolean; diff --git a/apps/admin/src/core/Auth/view/forms/SignInForm/Component.tsx b/apps/admin/src/core/Auth/view/forms/SignInForm/Component.tsx index b026d78..dbb6c72 100644 --- a/apps/admin/src/core/Auth/view/forms/SignInForm/Component.tsx +++ b/apps/admin/src/core/Auth/view/forms/SignInForm/Component.tsx @@ -5,7 +5,7 @@ import { useIntl } from 'react-intl'; import { FormElement, FormGrid, GridElement, InputText } from '#shared/forms/composed'; import { Button } from '#shared/ui/Button'; import { Form } from '#shared/ui/form'; -import { createValidationSchema, FormValues, SubmitFunc } from './form'; +import { createValidationSchema, type FormValues, type SubmitFunc } from './form'; export const SignInForm: React.FC<{ initialValues: FormValues; diff --git a/apps/admin/src/core/Auth/view/forms/SignInForm/form.ts b/apps/admin/src/core/Auth/view/forms/SignInForm/form.ts index 33bd13c..21fed96 100644 --- a/apps/admin/src/core/Auth/view/forms/SignInForm/form.ts +++ b/apps/admin/src/core/Auth/view/forms/SignInForm/form.ts @@ -1,4 +1,4 @@ -import { IntlShape } from 'react-intl'; +import type { IntlShape } from 'react-intl'; import z from 'zod'; export const createValidationSchema = (intl: IntlShape) => diff --git a/apps/admin/src/core/Auth/view/layouts/Branded.tsx b/apps/admin/src/core/Auth/view/layouts/Branded.tsx index 61f651d..a44f680 100755 --- a/apps/admin/src/core/Auth/view/layouts/Branded.tsx +++ b/apps/admin/src/core/Auth/view/layouts/Branded.tsx @@ -6,7 +6,7 @@ import { toAbsoluteUrl } from '#shared/lib/helpers'; import { Badge } from '#shared/ui/Badge'; import { Card, CardContent } from '#shared/ui/Card'; -export function BrandedLayout() { +export default function BrandedLayout() { const intl = useIntl(); return ( diff --git a/apps/admin/src/core/Auth/view/layouts/Classic.tsx b/apps/admin/src/core/Auth/view/layouts/Classic.tsx index e12822e..3620796 100755 --- a/apps/admin/src/core/Auth/view/layouts/Classic.tsx +++ b/apps/admin/src/core/Auth/view/layouts/Classic.tsx @@ -3,7 +3,7 @@ import { Link, Outlet } from 'react-router-dom'; import { toAbsoluteUrl } from '#shared/lib/helpers'; import { Card, CardContent } from '#shared/ui/Card'; -export function ClassicLayout() { +export default function ClassicLayout() { return (