Reorganize app workspaces and align Admin architecture

This commit is contained in:
Igor Martyniuk
2026-07-17 13:46:21 +00:00
parent ba691d3a0c
commit 0ba01cd111
183 changed files with 3745 additions and 4354 deletions
+4 -4
View File
@@ -26,7 +26,7 @@ yarn-error.log
# Platform specific
.DS_Store
/backend/public/
/backend/storage/
/backend/.adonisjs/
/backend/database/schema.ts
/apps/backend/public/
/apps/backend/storage/
/apps/backend/.adonisjs/
/apps/backend/database/schema.ts
+3 -3
View File
@@ -3,8 +3,8 @@
**/build/**
**/dist/**
**/coverage/**
backend/public/**
backend/storage/**
apps/backend/public/**
apps/backend/storage/**
# Handlebars generator templates contain application syntax that is not valid HTML.
backend/app/templates/**
apps/backend/app/templates/**
+10
View File
@@ -11,8 +11,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Changed
- Moved AdonisJS ESLint and Prettier configuration packages and Japa test dependencies from the
root package manifest to the backend workspace so workspace-specific dependencies are owned by
`apps/backend`.
- Updated the Admin frontend architecture so global providers belong to the `app` composition
layer under `app/providers` instead of being modeled as a separate architectural layer.
### Fixed
- Fixed Admin and Backend Docker startup on ARM64 by removing manually pinned platform-specific
optional dependencies and allowing npm to resolve the correct native packages for the container
architecture.
### Removed
## [1.1.0] - 2026-07-17
+3 -5
View File
@@ -2,15 +2,13 @@ FROM node:24-alpine3.22
WORKDIR /app
# Install workspace deps
# Copy all workspaces before installation so npm resolves workspace dependencies.
COPY package.json package-lock.json ./
COPY apps ./apps
COPY packages ./packages
RUN npm ci --force
# Copy project
COPY apps/admin ./apps/admin
COPY packages ./packages
WORKDIR /app/apps/admin
ENV CHOKIDAR_USEPOLLING=true
@@ -1,7 +1,7 @@
import { useLocation } from 'react-router-dom';
import { cn } from '@crux-kit/utils';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#providers/SettingsProvider';
import { useSettings } from '#app/providers/SettingsProvider';
import { SidebarHeader } from './SidebarHeader';
import { SidebarMenu } from './SidebarMenu';
@@ -2,9 +2,9 @@ import { ChevronFirst } from 'lucide-react';
import { Link } from 'react-router-dom';
import { cn } from '@crux-kit/utils';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { toAbsoluteUrl } from '#shared/lib/helpers';
import { Button } from '#shared/ui/Button';
import { useSettings } from '#providers/SettingsProvider';
export function SidebarHeader() {
const { settings, storeOption } = useSettings();
@@ -5,7 +5,7 @@ import { useEffect } from 'react';
import { Outlet } from 'react-router-dom';
import { useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#providers/SettingsProvider';
import { useSettings } from '#app/providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Sidebar } from './components/Sidebar';
@@ -3,8 +3,8 @@ import { useEffect, useState } from 'react'; // Add useState to the imports
import { useScrollPosition } from '@crux-kit/hooks';
import { cn } from '@crux-kit/utils';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Container } from '#shared/widgets/common/Container';
import { useSettings } from '#providers/SettingsProvider';
import { HeaderLogo } from './HeaderLogo';
import { HeaderTopbar } from './HeaderTopbar';
@@ -8,10 +8,10 @@ import { DateRange } from 'react-day-picker';
import { Outlet, useLocation } from 'react-router-dom';
import { useBodyClass } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { Calendar } from '#shared/ui/Calendar';
import { Popover, PopoverContent, PopoverTrigger } from '#shared/ui/Popover';
import { useSettings } from '#providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Navbar } from './components/Navbar';
@@ -6,8 +6,8 @@ import { useEffect } from 'react';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { useSettings } from '#providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Navbar } from './components/Navbar';
@@ -6,8 +6,8 @@ import { useEffect } from 'react';
import { Link, Outlet } from 'react-router-dom';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { useSettings } from '#providers/SettingsProvider';
// import { SearchDialog } from '#shared/partials/dialogs/search/search-dialog';
// import { NotificationsSheet } from '#shared/partials/topbar/notifications-sheet';
import { Footer } from './components/Footer';
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
import { useScrollPosition } from '@crux-kit/hooks';
import { cn } from '@crux-kit/utils';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Container } from '#shared/widgets/common/Container';
import { useSettings } from '#providers/SettingsProvider';
import { HeaderLogo } from './HeaderLogo';
import { HeaderTopbar } from './HeaderTopbar';
@@ -8,10 +8,10 @@ import { DateRange } from 'react-day-picker';
import { Outlet, useLocation } from 'react-router';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { Calendar } from '#shared/ui/Calendar';
import { Popover, PopoverContent, PopoverTrigger } from '#shared/ui/Popover';
import { useSettings } from '#providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Navbar } from './components/Navbar';
@@ -5,7 +5,7 @@ import React from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#providers/SettingsProvider';
import { useSettings } from '#app/providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Sidebar } from './components/Sidebar';
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
import { useScrollPosition } from '@crux-kit/hooks';
import { cn } from '@crux-kit/utils';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Container } from '#shared/widgets/common/Container';
import { useSettings } from '#providers/SettingsProvider';
import { HeaderLogo } from './HeaderLogo';
import { HeaderTopbar } from './HeaderTopbar';
@@ -8,10 +8,10 @@ import { DateRange } from 'react-day-picker';
import { Link, Outlet, useLocation } from 'react-router-dom';
import { useBodyClass } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { Calendar } from '#shared/ui/Calendar';
import { Popover, PopoverContent, PopoverTrigger } from '#shared/ui/Popover';
import { useSettings } from '#providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Toolbar, ToolbarActions, ToolbarHeading } from './components/Toolbar';
@@ -6,8 +6,8 @@ import { useEffect } from 'react';
import { Link, Outlet } from 'react-router-dom';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { useSettings } from '#providers/SettingsProvider';
// import { SearchDialog } from '#shared/partials/dialogs/search/search-dialog';
// import { ChatSheet } from '#shared/partials/topbar/chat-sheet';
import { Footer } from './components/Footer';
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
import { useScrollPosition } from '@crux-kit/hooks';
import { cn } from '@crux-kit/utils';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Container } from '#shared/widgets/common/Container';
import { useSettings } from '#providers/SettingsProvider';
import { HeaderLogo } from './HeaderLogo';
import { HeaderSearch } from './HeaderSearch';
import { HeaderTopbar } from './HeaderTopbar';
@@ -8,10 +8,10 @@ import { DateRange } from 'react-day-picker';
import { Link, Outlet, useLocation } from 'react-router';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { Calendar } from '#shared/ui/Calendar';
import { Popover, PopoverContent, PopoverTrigger } from '#shared/ui/Popover';
import { useSettings } from '#providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Navbar } from './components/Navbar';
@@ -8,10 +8,10 @@ import { DateRange } from 'react-day-picker';
import { Link, Outlet } from 'react-router-dom';
import { useBodyClass, useIsMobile } from '@crux-kit/hooks';
import { LayoutNames } from '#app/config/types';
import { useSettings } from '#app/providers/SettingsProvider';
import { Button } from '#shared/ui/Button';
import { Calendar } from '#shared/ui/Calendar';
import { Popover, PopoverContent, PopoverTrigger } from '#shared/ui/Popover';
import { useSettings } from '#providers/SettingsProvider';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Sidebar } from './components/Sidebar';
@@ -1,4 +1,4 @@
import { queryClient } from '#providers/QueryProvider';
import { queryClient } from '#app/providers/QueryProvider';
import { adminDetailedKeyBuilder, adminListKeyBuilder, permissionListKeyBuilder, roleDetailedKeyBuilder, roleListKeyBuilder } from './keys';
export const invalidateAdminList = async () => {
@@ -1,10 +1,10 @@
import { PropsWithChildren, useState } 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 UserMapper from '#core/Auth/data/mappers/UserMapper';
import * as authHelper from '#core/Auth/data/stores/localStorage';
import { AuthModel, AuthUser } from '#core/Auth/entities';
import { queryClient } from '#providers/QueryProvider';
import { AuthContext } from './context';
export function AuthProvider({ children }: PropsWithChildren) {
@@ -1,8 +1,8 @@
import { InfiniteData, QueryKey } from '@tanstack/react-query';
import { queryClient } from '#app/providers/QueryProvider';
import { EntryDTO } from '#core/FileManager/data/api/dto';
import { AUDIO_EXTENSIONS, getFileExtension, IMAGE_EXTENSIONS, PDF_EXTENSIONS, PLAINTEXT_EXTENSIONS, VIDEO_EXTENSIONS } from '#core/FileManager/shared/file/extension';
import { ensureLeadingSlash } from '#core/FileManager/shared/path/leadingSlash';
import { queryClient } from '#providers/QueryProvider';
import { fetchEntries } from './entries';
import { entriesKeyBuilder, EntriesQueryPayload } from './keys';
@@ -93,7 +93,7 @@ export default function TutorialSection() {
Compare frontend permission constants in{' '}
<code>apps/admin/src/modules/&lt;name&gt;/config/meta.ts</code> with backend
permissions in{' '}
<code>backend/modules/&lt;ModuleName&gt;/config/permissions.ts</code>.
<code>apps/backend/modules/&lt;ModuleName&gt;/config/permissions.ts</code>.
</li>
<li>Confirm the current admin role owns the generated view permission.</li>
<li>Check that the module manifest exposes its menu and routes.</li>
@@ -1,6 +1,6 @@
import { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { queryClient } from '#app/providers/QueryProvider';
import { getAuth, removeAuth } from '#core/Auth/data/stores/localStorage';
import { queryClient } from '#providers/QueryProvider';
export function requestInterceptorSuccess(
config: InternalAxiosRequestConfig
+1 -1
View File
@@ -1,7 +1,7 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import AppProviders from '#app/providers/AppProviders';
import AppRouting from '#app/routing/AppRouting';
import AppProviders from '#providers/AppProviders';
import './app/css/styles.css';
/**
@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { queryClient } from '#app/providers/QueryProvider';
import { axiosClient } from '#infrastructure/axios';
import { queryClient } from '#providers/QueryProvider';
import { MODULE_API_ENDPOINT } from '../config/meta';
import { Entity } from '../entities/Entity';
import { detailedKeyBuilder } from './keys';
+1 -1
View File
@@ -1,8 +1,8 @@
import { keepPreviousData, useQuery } from '@tanstack/react-query';
import React from 'react';
import { queryClient } from '#app/providers/QueryProvider';
import { axiosClient } from '#infrastructure/axios';
import { getPaginationInfo, ListQueryParams, SuccessListResponse } from '#infrastructure/reactQuery';
import { queryClient } from '#providers/QueryProvider';
import { MODULE_API_ENDPOINT } from '../config/meta';
import { Entity } from '../entities/Entity';
import { listKeyBuilder } from './keys';
@@ -2,7 +2,7 @@ import { Editor as TinyMCEEditorReact } from '@tinymce/tinymce-react';
import { useTheme } from 'next-themes';
import React, { useRef } from 'react';
import { Editor } from 'tinymce';
import { useLanguage } from '#providers/I18nProvider';
import { useLanguage } from '#app/providers/I18nProvider';
import { Statusbar } from './components/Statusbar';
import { Toolbar } from './components/Toolbar';
import { apiKey, createConfig, defaultToolbarLayout, ToolbarLayout, ToolbarOptions } from './config';
@@ -1,8 +1,8 @@
import { useFormContext } from 'react-hook-form';
import { InputTimeFormat } from '@crux-kit/types';
import { useLanguage } from '#app/providers/I18nProvider';
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '#shared/ui/form';
import { TimeInput } from '#shared/ui/TimeInput';
import { useLanguage } from '#providers/I18nProvider';
export const InputTime: React.FC<{
name: string;
@@ -4,6 +4,7 @@ import { ReactNode } from 'react';
import { useIntl } from 'react-intl';
import { Link } from 'react-router';
import { Language } from '#app/i18n/types';
import { useLanguage } from '#app/providers/I18nProvider';
import { IntlName } from '#infrastructure/IntlName';
import { useAuth } from '#core/Auth/providers/AuthProvider';
import { Avatar, AvatarFallback, AvatarImage } from '#shared/ui/Avatar';
@@ -11,7 +12,6 @@ import { Badge } from '#shared/ui/Badge';
import { Button } from '#shared/ui/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from '#shared/ui/DropdownMenu';
import { Switch } from '#shared/ui/Switch';
import { useLanguage } from '#providers/I18nProvider';
export function UserDropdownMenu({ trigger }: { trigger: ReactNode }) {
const intl = useIntl();
@@ -1,7 +1,7 @@
import { cva, type VariantProps } from 'class-variance-authority';
import { type ReactNode } from 'react';
import { cn } from '@crux-kit/utils';
import { useSettings } from '#providers/SettingsProvider';
import { useSettings } from '#app/providers/SettingsProvider';
const containerVariants = cva('w-full mx-auto px-4 lg:px-6', {
variants: {
@@ -3,17 +3,15 @@ FROM node:24-alpine3.22
WORKDIR /app
# Copy only root workspace files first
# Copy all workspaces before installation so npm can resolve their dependencies.
COPY package.json package-lock.json ./
# Install workspace dependencies (monorepo-aware)
RUN npm ci --force
# Copy backend source
COPY backend ./backend
COPY apps ./apps
COPY packages ./packages
WORKDIR /app/backend
# Install workspace dependencies (monorepo-aware).
RUN npm ci --force
WORKDIR /app/apps/backend
# Expose dev port
EXPOSE 3333
@@ -136,8 +136,9 @@ export default class MakeModuleController {
const modulePath = join(appBase, 'modules', moduleName)
const snake = this.snakeCase(table)
const clientBase = join(appBase, '..', 'apps', 'admin', 'src', 'modules', moduleName)
const templateModuleDir = join(appBase, '..', 'apps', 'admin', 'src', 'modules', '_template')
// Backend and admin are sibling workspaces under apps/.
const clientBase = join(appBase, '..', 'admin', 'src', 'modules', moduleName)
const templateModuleDir = join(appBase, '..', 'admin', 'src', 'modules', '_template')
const paths = {
// backend

Some files were not shown because too many files have changed in this diff Show More