diff --git a/.eslintignore b/.eslintignore index f62f3155a..744bcc2c5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -**/*/node_modules \ No newline at end of file +**/*/node_modules +prettier.config.js \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..4df5f26a3 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +prefer-workspace-packages=true +node-linker=hoisted diff --git a/.prettierignore b/.prettierignore index aeeb05997..d001e1440 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,4 +9,7 @@ pnpm-lock.yaml /packages/app-builder/src/utils/routes/routes.ts # marble-api -/packages/marble-api/src/generated \ No newline at end of file +/packages/marble-api/src/generated + +# ui-design-system +/packages/ui-design-system/storybook-static/ \ No newline at end of file diff --git a/.tool-versions b/.tool-versions index 9bf3213a7..e5cad67a8 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ nodejs 20.10.0 -pnpm 8.11.0 +pnpm 8.13.1 diff --git a/package.json b/package.json index 323149297..d2fe1622d 100644 --- a/package.json +++ b/package.json @@ -2,24 +2,26 @@ "name": "marble-front", "version": "0.0.0", "license": "MIT", + "private": true, + "type": "module", + "sideEffect": false, + "workspaces": [ + "packages/*" + ], "scripts": { "format:write": "prettier --write .", "format:check": "prettier --check .", "test:all": "vitest" }, - "private": true, "devDependencies": { "@vitejs/plugin-react": "4.2.1", - "@vitest/coverage-v8": "^1.0.1", - "@vitest/ui": "1.0.1", - "prettier": "^3.1.0", - "prettier-plugin-tailwindcss": "^0.5.9", + "@vitest/coverage-v8": "^1.1.1", + "@vitest/ui": "1.1.1", + "prettier": "^3.1.1", + "prettier-plugin-tailwindcss": "^0.5.10", "react-refresh": "^0.14.0", - "typescript": "5.3.2", - "vite-tsconfig-paths": "^4.2.1", - "vitest": "1.0.1" - }, - "workspaces": [ - "packages/*" - ] + "typescript": "5.3.3", + "vite-tsconfig-paths": "^4.2.3", + "vitest": "1.1.1" + } } diff --git a/packages/app-builder/.eslintrc.js b/packages/app-builder/.eslintrc.cjs similarity index 91% rename from packages/app-builder/.eslintrc.js rename to packages/app-builder/.eslintrc.cjs index beab29f58..8cad21043 100644 --- a/packages/app-builder/.eslintrc.js +++ b/packages/app-builder/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { extends: ['@marble/eslint-config/react'], settings: { tailwindcss: { - config: join(__dirname, 'tailwind.config.js'), + config: join(__dirname, 'tailwind.config.ts'), }, }, ignorePatterns: ['/node_modules/', '/.cache/', '/build/', '/public/build/'], diff --git a/packages/app-builder/README.md b/packages/app-builder/README.md index fbdc561b4..b8fedd852 100644 --- a/packages/app-builder/README.md +++ b/packages/app-builder/README.md @@ -40,7 +40,7 @@ If the component is part of the marble design system, you need to create it insi 1. The component is mostly "presentationnal" (not connected to external data source needing loaders/actions): create the new component inside `src/components/*`. -2. The component is considered a [full-stack componente](https://www.epicweb.dev/full-stack-components) (connected to external data source needing loaders/actions): create the new component inside `src/routes/ressources/*`. +2. The component is considered a [full-stack componente](https://www.epicweb.dev/full-stack-components) (connected to external data source needing loaders/actions): create the new component inside `src/routes/ressources+/*`. > NB: in both cases, look at existing components to see how to structure your component and/or help you decide what feats your needs. diff --git a/packages/app-builder/package.json b/packages/app-builder/package.json index b73344494..055bf0635 100644 --- a/packages/app-builder/package.json +++ b/packages/app-builder/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "description": "", "main": "index.js", + "private": true, + "sideEffects": false, + "type": "module", "scripts": { "lint": "eslint .", "type-check": "npx tsc --noEmit", @@ -16,28 +19,28 @@ "license": "ISC", "devDependencies": { "@marble/eslint-config": "workspace:^", - "@remix-run/dev": "^1.19.3", + "@remix-run/dev": "^2.4.1", "@segment/analytics-next": "^1.62.0", - "@sentry/cli": "^2.23.0", + "@sentry/cli": "^2.23.2", "@types/autosuggest-highlight": "^3.2.3", - "@types/qs": "^6.9.10", - "@types/react": "18.2.42", - "@types/react-dom": "18.2.17", + "@types/qs": "^6.9.11", + "@types/react": "18.2.46", + "@types/react-dom": "18.2.18", "@types/swagger-ui-react": "^4.18.3", "dotenv-cli": "^7.3.0", - "eslint": "~8.55.0", - "eslint-plugin-tailwindcss": "^3.13.0", + "eslint-plugin-tailwindcss": "^3.13.1", "jsdom": "23.0.1", - "ora": "^7.0.1", - "tailwindcss": "3.3.6", - "tsx": "^4.6.2" + "ora": "^8.0.1", + "postcss": "^8.4.32", + "tailwindcss": "3.4.0", + "tsx": "^4.7.0" }, "dependencies": { - "@ariakit/react": "^0.3.8", + "@ariakit/react": "^0.3.12", "@conform-to/react": "^0.9.1", "@conform-to/zod": "^0.9.1", "@hookform/devtools": "^4.3.1", - "@hookform/resolvers": "^3.3.2", + "@hookform/resolvers": "^3.3.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", @@ -45,47 +48,49 @@ "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.7", - "@remix-run/node": "^1.19.3", - "@remix-run/react": "^1.19.3", - "@remix-run/serve": "^1.19.3", + "@remix-run/node": "^2.4.1", + "@remix-run/react": "^2.4.1", + "@remix-run/serve": "^2.4.1", "@segment/snippet": "^5.2.0", - "@sentry/remix": "^7.85.0", - "@tanstack/react-table": "^8.10.7", + "@sentry/remix": "^7.91.0", + "@tanstack/react-table": "^8.11.2", "autosuggest-highlight": "^3.3.4", "class-variance-authority": "^0.7.0", - "clsx": "^1.2.1", + "clsx": "^2.1.0", "cronstrue": "^2.47.0", - "date-fns": "^2.30.0", + "crypto-js": "^4.2.0", + "date-fns": "^3.0.6", "dotenv": "^16.3.1", "firebase": "^9.23.0", - "i18next": "^23.7.7", + "i18next": "^23.7.13", "i18next-browser-languagedetector": "^7.2.0", - "i18next-fs-backend": "^2.3.0", + "i18next-fs-backend": "^2.3.1", "i18next-http-backend": "^2.4.2", - "isbot": "^3.7.1", - "marble-api": "workspace:^", + "isbot": "^4.1.1", + "marble-api": "workspace:*", "match-sorter": "^6.3.1", - "nanoid": "^4.0.2", - "oazapfts": "^4.11.2", + "nanoid": "^5.0.4", + "oazapfts": "^4.12.0", "qs": "^6.11.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.48.2", "react-hot-toast": "^2.4.1", - "react-i18next": "^13.5.0", + "react-i18next": "^14.0.0", "react-json-view-lite": "^0.9.8", - "remeda": "^1.29.0", - "remix": "^1.19.3", - "remix-i18next": "^5.4.0", - "remix-utils": "^6.6.0", + "remeda": "^1.33.0", + "remix": "^2.4.1", + "remix-flat-routes": "^0.6.4", + "remix-i18next": "^5.5.0", + "remix-utils": "^7.5.0", "short-uuid": "^4.2.2", - "swagger-ui-react": "^5.10.3", + "swagger-ui-react": "^5.10.5", "temporal-polyfill": "^0.1.1", "tiny-invariant": "^1.3.1", - "typescript-utils": "workspace:^", - "ui-design-system": "workspace:^", - "ui-icons": "workspace:^", + "typescript-utils": "workspace:*", + "ui-design-system": "workspace:*", + "ui-icons": "workspace:*", "winston": "^3.11.0", "zod": "^3.22.4" } diff --git a/packages/app-builder/postcss.config.js b/packages/app-builder/postcss.config.js new file mode 100644 index 000000000..2aa7205d4 --- /dev/null +++ b/packages/app-builder/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/app-builder/remix.config.js b/packages/app-builder/remix.config.js index 421c67094..51ba85cb8 100644 --- a/packages/app-builder/remix.config.js +++ b/packages/app-builder/remix.config.js @@ -1,22 +1,35 @@ -/** @type {import('@remix-run/dev').AppConfig} */ -module.exports = { - serverDependenciesToBundle: ['nanoid'], - serverModuleFormat: 'cjs', - appDirectory: 'src', - ignoredRouteFiles: ['**/.*'], +import { flatRoutes } from 'remix-flat-routes'; + +const appDirectory = 'src'; + +/** + * @type {import('@remix-run/dev').AppConfig} + */ +export default { + serverModuleFormat: 'esm', + serverDependenciesToBundle: ['remix-i18next', 'react-dropzone'], + serverPlatform: 'node', + appDirectory, + browserNodeBuiltinsPolyfill: { modules: { process: true } }, + ignoredRouteFiles: ['**/*'], watchPaths: [ '../ui-design-system/**/*', '../tailwind-preset/**/*', '../@ui-icons/**/*', '../typescript-utils/**/*', - '../marble-api/**/*', ], - future: { - v2_errorBoundary: true, - v2_meta: true, - v2_normalizeFormMethod: true, - v2_routeConvention: false, - v2_dev: true, - }, + future: {}, tailwind: true, + postcss: true, + routes: async (defineRoutes) => { + return flatRoutes('routes', defineRoutes, { + appDir: appDirectory, + ignoredRouteFiles: [ + '.*', + '**/*.css', + '**/*.test.{js,jsx,ts,tsx}', + '**/__*.*', + ], + }); + }, }; diff --git a/packages/app-builder/src/components/Cases/CaseFiles.tsx b/packages/app-builder/src/components/Cases/CaseFiles.tsx index 4fd8bb292..71389b1f1 100644 --- a/packages/app-builder/src/components/Cases/CaseFiles.tsx +++ b/packages/app-builder/src/components/Cases/CaseFiles.tsx @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import toast from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { last } from 'remeda'; -import { ClientOnly } from 'remix-utils'; +import { ClientOnly } from 'remix-utils/client-only'; import { Button, Collapsible, Table, useVirtualTable } from 'ui-design-system'; import { casesI18n } from './cases-i18n'; diff --git a/packages/app-builder/src/components/Cases/CaseInformation.tsx b/packages/app-builder/src/components/Cases/CaseInformation.tsx index e1205ae8f..0af5cae0a 100644 --- a/packages/app-builder/src/components/Cases/CaseInformation.tsx +++ b/packages/app-builder/src/components/Cases/CaseInformation.tsx @@ -1,7 +1,7 @@ import { type CurrentUser } from '@app-builder/models'; -import { EditCaseInbox } from '@app-builder/routes/ressources/cases/edit-inbox'; -import { EditCaseName } from '@app-builder/routes/ressources/cases/edit-name'; -import { EditCaseTags } from '@app-builder/routes/ressources/cases/edit-tags'; +import { EditCaseInbox } from '@app-builder/routes/ressources+/cases+/edit-inbox'; +import { EditCaseName } from '@app-builder/routes/ressources+/cases+/edit-name'; +import { EditCaseTags } from '@app-builder/routes/ressources+/cases+/edit-tags'; import { formatDateTime } from '@app-builder/utils/format'; import { type Case, type InboxDto } from 'marble-api'; import { useTranslation } from 'react-i18next'; diff --git a/packages/app-builder/src/components/Cases/CaseRightPanel.tsx b/packages/app-builder/src/components/Cases/CaseRightPanel.tsx index 89a427091..7227be865 100644 --- a/packages/app-builder/src/components/Cases/CaseRightPanel.tsx +++ b/packages/app-builder/src/components/Cases/CaseRightPanel.tsx @@ -2,7 +2,7 @@ import { createRightPanel, type RightPanelRootProps, } from '@app-builder/components/RightPanel'; -import { CreateCase } from '@app-builder/routes/ressources/cases/create-case'; +import { CreateCase } from '@app-builder/routes/ressources+/cases+/create-case'; import { createSimpleContext } from '@app-builder/utils/create-context'; import { type DialogTriggerProps } from '@radix-ui/react-dialog'; import { useReducer } from 'react'; diff --git a/packages/app-builder/src/components/Cases/Filters/CasesFiltersBar.tsx b/packages/app-builder/src/components/Cases/Filters/CasesFiltersBar.tsx index 20d9027e9..41ea076d2 100644 --- a/packages/app-builder/src/components/Cases/Filters/CasesFiltersBar.tsx +++ b/packages/app-builder/src/components/Cases/Filters/CasesFiltersBar.tsx @@ -75,7 +75,7 @@ export function CasesFiltersBar() { - + ); diff --git a/packages/app-builder/src/components/Decisions/DecisionRightPanel.tsx b/packages/app-builder/src/components/Decisions/DecisionRightPanel.tsx index 6b3785fbe..5841dce6b 100644 --- a/packages/app-builder/src/components/Decisions/DecisionRightPanel.tsx +++ b/packages/app-builder/src/components/Decisions/DecisionRightPanel.tsx @@ -2,7 +2,7 @@ import { createRightPanel, type RightPanelRootProps, } from '@app-builder/components/RightPanel'; -import { AddToCase } from '@app-builder/routes/ressources/cases/add-to-case'; +import { AddToCase } from '@app-builder/routes/ressources+/cases+/add-to-case'; import { createSimpleContext } from '@app-builder/utils/create-context'; import { useReducer } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/app-builder/src/components/Decisions/Filters/DecisionFiltersBar.tsx b/packages/app-builder/src/components/Decisions/Filters/DecisionFiltersBar.tsx index bedaf7218..8be866b0e 100644 --- a/packages/app-builder/src/components/Decisions/Filters/DecisionFiltersBar.tsx +++ b/packages/app-builder/src/components/Decisions/Filters/DecisionFiltersBar.tsx @@ -75,7 +75,7 @@ export function DecisionFiltersBar() { - + ); diff --git a/packages/app-builder/src/components/Navigation.tsx b/packages/app-builder/src/components/Navigation.tsx index 5eeb124f5..5c6bc3ee7 100644 --- a/packages/app-builder/src/components/Navigation.tsx +++ b/packages/app-builder/src/components/Navigation.tsx @@ -2,13 +2,14 @@ import { NavLink } from '@remix-run/react'; import { cva } from 'class-variance-authority'; import clsx from 'clsx'; import { type Namespace, type ParseKeys } from 'i18next'; +import { type IconProps } from 'packages/ui-icons/src/Icon'; import type React from 'react'; import { useTranslation } from 'react-i18next'; export const navigationI18n = ['navigation'] satisfies Namespace; export interface SidebarLinkProps { - Icon: (props: React.SVGProps) => JSX.Element; + Icon: (props: Omit) => JSX.Element; labelTKey: ParseKeys<['navigation']>; to: string; } @@ -43,7 +44,7 @@ export function SidebarLink({ Icon, labelTKey, to }: SidebarLinkProps) { export interface SidebarButtonProps extends Omit, 'children'> { - Icon: (props: React.SVGProps) => JSX.Element; + Icon: (props: Omit) => JSX.Element; labelTKey: ParseKeys<['navigation']>; } @@ -56,14 +57,12 @@ export function SidebarButton({ const { t } = useTranslation(navigationI18n); return ( -
  • - -
  • + ); } diff --git a/packages/app-builder/src/components/ScheduledExecutions/ScheduledExecutionDetails.tsx b/packages/app-builder/src/components/ScheduledExecutions/ScheduledExecutionDetails.tsx index fae2d78dc..88f68e59f 100644 --- a/packages/app-builder/src/components/ScheduledExecutions/ScheduledExecutionDetails.tsx +++ b/packages/app-builder/src/components/ScheduledExecutions/ScheduledExecutionDetails.tsx @@ -4,7 +4,7 @@ import { } from '@app-builder/services/DownloadDecisionsService'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; -import { ClientOnly } from 'remix-utils'; +import { ClientOnly } from 'remix-utils/client-only'; import { Button } from 'ui-design-system'; import { scheduledExecutionI18n } from './scheduledExecution-i18n'; diff --git a/packages/app-builder/src/entry.server.tsx b/packages/app-builder/src/entry.server.tsx index 4e798d0a3..8ae2f1e98 100644 --- a/packages/app-builder/src/entry.server.tsx +++ b/packages/app-builder/src/entry.server.tsx @@ -1,11 +1,11 @@ import { - type DataFunctionArgs, + createReadableStreamFromReadable, type EntryContext, - Response, + type HandleErrorFunction, } from '@remix-run/node'; import { RemixServer } from '@remix-run/react'; import * as Sentry from '@sentry/remix'; -import isbot from 'isbot'; +import { isbot } from 'isbot'; import { renderToPipeableStream } from 'react-dom/server'; import { I18nextProvider } from 'react-i18next'; import { PassThrough } from 'stream'; @@ -28,13 +28,13 @@ export default async function handleRequest( ); const App = ( - // @ts-expect-error: weird TS bug ); - return isbot(request.headers.get('user-agent')) + const userAgent = request.headers.get('user-agent'); + return userAgent && isbot(userAgent) ? handleBotRequest(responseStatusCode, responseHeaders, App) : handleBrowserRequest(responseStatusCode, responseHeaders, App); } @@ -54,7 +54,7 @@ function handleBotRequest( responseHeaders.set('Content-Type', 'text/html'); resolve( - new Response(body, { + new Response(createReadableStreamFromReadable(body), { headers: responseHeaders, status: didError ? 500 : responseStatusCode, }), @@ -91,7 +91,7 @@ function handleBrowserRequest( responseHeaders.set('Content-Type', 'text/html'); resolve( - new Response(body, { + new Response(createReadableStreamFromReadable(body), { headers: responseHeaders, status: didError ? 500 : responseStatusCode, }), @@ -122,14 +122,11 @@ Sentry.init({ tracesSampleRate: 1.0, }); -export async function handleError( - error: unknown, - { request }: DataFunctionArgs, -) { +export const handleError: HandleErrorFunction = (error, { request }) => { if (error instanceof Error) { - await Sentry.captureRemixServerException(error, 'remix.server', request); + void Sentry.captureRemixServerException(error, 'remix.server', request); } else { // Optionally capture non-Error objects Sentry.captureException(error); } -} +}; diff --git a/packages/app-builder/src/repositories/CaseRepository.ts b/packages/app-builder/src/repositories/CaseRepository.ts index e776a4910..67e16fa92 100644 --- a/packages/app-builder/src/repositories/CaseRepository.ts +++ b/packages/app-builder/src/repositories/CaseRepository.ts @@ -2,7 +2,7 @@ import { type PaginatedResponse } from '@app-builder/components/PaginationButton import { type MarbleApi } from '@app-builder/infra/marble-api'; import { adaptCaseDetailDto, type CaseDetail } from '@app-builder/models/cases'; import { type FiltersWithPagination } from '@app-builder/models/pagination'; -import { add } from 'date-fns'; +import { add } from 'date-fns/add'; import { type Case, type CaseStatus, type UpdateCaseBody } from 'marble-api'; import { Temporal } from 'temporal-polyfill'; diff --git a/packages/app-builder/src/repositories/DecisionRepository.ts b/packages/app-builder/src/repositories/DecisionRepository.ts index b1f88d904..355443311 100644 --- a/packages/app-builder/src/repositories/DecisionRepository.ts +++ b/packages/app-builder/src/repositories/DecisionRepository.ts @@ -1,7 +1,7 @@ import { type PaginatedResponse } from '@app-builder/components'; import { type MarbleApi } from '@app-builder/infra/marble-api'; import { type FiltersWithPagination } from '@app-builder/models/pagination'; -import { add } from 'date-fns'; +import { add } from 'date-fns/add'; import { type Decision, type Outcome } from 'marble-api'; import { Temporal } from 'temporal-polyfill'; diff --git a/packages/app-builder/src/repositories/SessionStorageRepositories/CsrfStorageRepository.ts b/packages/app-builder/src/repositories/SessionStorageRepositories/CsrfStorageRepository.ts index 2ee46ba5d..cbacadfda 100644 --- a/packages/app-builder/src/repositories/SessionStorageRepositories/CsrfStorageRepository.ts +++ b/packages/app-builder/src/repositories/SessionStorageRepositories/CsrfStorageRepository.ts @@ -1,13 +1,13 @@ -import { createCookie, createCookieSessionStorage } from '@remix-run/node'; +import { createCookie } from '@remix-run/node'; import { type SessionStorageRepositoryOptions } from './SessionStorageRepository'; -export function getCsrfStorageRepository({ +export function getCsrfCookie({ maxAge, secrets, secure, }: SessionStorageRepositoryOptions) { - const csrfCookie = createCookie('csrf', { + return createCookie('csrf', { maxAge, sameSite: 'lax', // this helps with CSRF path: '/', // remember to add this so the cookie will work in all routes @@ -15,13 +15,6 @@ export function getCsrfStorageRepository({ secrets, secure, }); - - // export the whole sessionStorage object - const csrfStorage = createCookieSessionStorage({ - cookie: csrfCookie, - }); - - return { csrfStorage }; } -export type CsrfStorageRepository = ReturnType; +export type CsrfCookie = ReturnType; diff --git a/packages/app-builder/src/repositories/init.server.ts b/packages/app-builder/src/repositories/init.server.ts index 1cc503b61..54a553b7f 100644 --- a/packages/app-builder/src/repositories/init.server.ts +++ b/packages/app-builder/src/repositories/init.server.ts @@ -9,7 +9,7 @@ import { getOrganizationRepository } from './OrganizationRepository'; import { getScenarioRepository } from './ScenarioRepository'; import { getAuthStorageRepository, - getCsrfStorageRepository, + getCsrfCookie, getToastStorageRepository, type SessionStorageRepositoryOptions, } from './SessionStorageRepositories'; @@ -26,9 +26,7 @@ export function makeServerRepositories({ authStorageRepository: getAuthStorageRepository( sessionStorageRepositoryOptions, ), - csrfStorageRepository: getCsrfStorageRepository( - sessionStorageRepositoryOptions, - ), + csrfCookie: getCsrfCookie(sessionStorageRepositoryOptions), toastStorageRepository: getToastStorageRepository( sessionStorageRepositoryOptions, ), diff --git a/packages/app-builder/src/root.tsx b/packages/app-builder/src/root.tsx index 82567e0f0..155f79cc8 100644 --- a/packages/app-builder/src/root.tsx +++ b/packages/app-builder/src/root.tsx @@ -1,8 +1,8 @@ import { json, type LinksFunction, - type LoaderArgs, - type V2_MetaFunction, + type LoaderFunctionArgs, + type MetaFunction, } from '@remix-run/node'; import { Links, @@ -18,12 +18,9 @@ import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'; import { type Namespace } from 'i18next'; import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { - AuthenticityTokenProvider, - ClientOnly, - createAuthenticityToken, - ExternalScripts, -} from 'remix-utils'; +import { ClientOnly } from 'remix-utils/client-only'; +import { AuthenticityTokenProvider } from 'remix-utils/csrf/react'; +import { ExternalScripts } from 'remix-utils/external-scripts'; import { Tooltip } from 'ui-design-system'; import { iconsSVGSpriteHref, Logo, logosSVGSpriteHref } from 'ui-icons'; @@ -62,41 +59,43 @@ export const links: LinksFunction = () => [ { rel: 'icon', href: '/favicon.ico' }, ]; -export const loader = async ({ request }: LoaderArgs) => { - const { i18nextService, toastSessionService, csrfSessionService } = - serverServices; +export async function loader({ request }: LoaderFunctionArgs) { + const { i18nextService, toastSessionService, csrfService } = serverServices; const locale = await i18nextService.getLocale(request); const toastSession = await toastSessionService.getSession(request); - const csrfSession = await csrfSessionService.getSession(request); + const [csrfToken, csrfCookieHeader] = await csrfService.commitToken(request); const toastMessage = getToastMessage(toastSession); - const csrf = createAuthenticityToken(csrfSession); const ENV = getClientEnvVars(); + const headers = new Headers(); + headers.append( + 'set-cookie', + await toastSessionService.commitSession(toastSession), + ); + if (csrfCookieHeader) headers.append('set-cookie', csrfCookieHeader); + return json( { ENV, locale, - csrf, + csrf: csrfToken, toastMessage, segmentScript: getSegmentScript(), }, { - headers: [ - ['Set-Cookie', await toastSessionService.commitSession(toastSession)], - ['Set-Cookie', await csrfSessionService.commitSession(csrfSession)], - ], + headers, }, ); -}; +} export const handle = { i18n: ['common', 'navigation'] satisfies Namespace, }; -export const meta: V2_MetaFunction = () => [ +export const meta: MetaFunction = () => [ { charset: 'utf-8', }, diff --git a/packages/app-builder/src/routes/__builder/$.tsx b/packages/app-builder/src/routes/_builder+/$.tsx similarity index 65% rename from packages/app-builder/src/routes/__builder/$.tsx rename to packages/app-builder/src/routes/_builder+/$.tsx index 93a5c40ce..68f0674c2 100644 --- a/packages/app-builder/src/routes/__builder/$.tsx +++ b/packages/app-builder/src/routes/_builder+/$.tsx @@ -1,7 +1,7 @@ import { serverServices } from '@app-builder/services/init.server'; -import { type LoaderArgs } from '@remix-run/node'; +import { type LoaderFunctionArgs } from '@remix-run/node'; -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { const { authService } = serverServices; return authService.isAuthenticated(request, { successRedirect: '/scenarios', diff --git a/packages/app-builder/src/routes/__builder.tsx b/packages/app-builder/src/routes/_builder+/_layout.tsx similarity index 93% rename from packages/app-builder/src/routes/__builder.tsx rename to packages/app-builder/src/routes/_builder+/_layout.tsx index a328edb23..6f2222683 100644 --- a/packages/app-builder/src/routes/__builder.tsx +++ b/packages/app-builder/src/routes/_builder+/_layout.tsx @@ -5,6 +5,8 @@ import { SidebarLink, } from '@app-builder/components'; import { isAdmin } from '@app-builder/models'; +import { useRefreshToken } from '@app-builder/routes/ressources+/auth+/refresh'; +import { LanguagePicker } from '@app-builder/routes/ressources+/user+/language'; import { ChatlioWidget } from '@app-builder/services/chatlio/ChatlioWidget'; import { chatlioScript } from '@app-builder/services/chatlio/script'; import { serverServices } from '@app-builder/services/init.server'; @@ -15,8 +17,9 @@ import { useSegmentIdentification, } from '@app-builder/services/segment'; import { getFullName } from '@app-builder/services/user'; +import { getRoute } from '@app-builder/utils/routes'; import * as Popover from '@radix-ui/react-popover'; -import { json, type LoaderArgs } from '@remix-run/node'; +import { json, type LoaderFunctionArgs } from '@remix-run/node'; import { Form, Outlet, useLoaderData } from '@remix-run/react'; import clsx from 'clsx'; import { type Namespace } from 'i18next'; @@ -25,11 +28,7 @@ import { useTranslation } from 'react-i18next'; import { Avatar, Button, ScrollArea } from 'ui-design-system'; import { Icon, Logo } from 'ui-icons'; -import { getRoute } from '../utils/routes'; -import { useRefreshToken } from './ressources/auth/refresh'; -import { LanguagePicker } from './ressources/user/language'; - -export async function loader({ request }: LoaderArgs) { +export async function loader({ request }: LoaderFunctionArgs) { const { authService } = serverServices; const { user, organization } = await authService.isAuthenticated(request, { failureRedirect: '/login', @@ -123,7 +122,10 @@ export default function Builder() {
    -
    + - +
    diff --git a/packages/app-builder/src/routes/ressources/lists/edit.tsx b/packages/app-builder/src/routes/ressources+/lists+/edit.tsx similarity index 95% rename from packages/app-builder/src/routes/ressources/lists/edit.tsx rename to packages/app-builder/src/routes/ressources+/lists+/edit.tsx index f7043eebd..1eb062dc5 100644 --- a/packages/app-builder/src/routes/ressources/lists/edit.tsx +++ b/packages/app-builder/src/routes/ressources+/lists+/edit.tsx @@ -7,8 +7,9 @@ import { } from '@app-builder/components/Form'; import { serverServices } from '@app-builder/services/init.server'; import { parseFormSafe } from '@app-builder/utils/input-validation'; +import { getRoute } from '@app-builder/utils/routes'; import { zodResolver } from '@hookform/resolvers/zod'; -import { type ActionArgs, json } from '@remix-run/node'; +import { type ActionFunctionArgs, json } from '@remix-run/node'; import { useFetcher } from '@remix-run/react'; import { type Namespace } from 'i18next'; import { useEffect, useState } from 'react'; @@ -28,7 +29,7 @@ const editListFormSchema = z.object({ description: z.string(), }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const { authService } = serverServices; const { apiClient } = await authService.isAuthenticated(request, { failureRedirect: '/login', @@ -101,7 +102,7 @@ export function EditList({ onSubmit={({ formData }) => { fetcher.submit(formData, { method: 'PATCH', - action: '/ressources/lists/edit', + action: getRoute('/ressources/lists/edit'), }); }} > diff --git a/packages/app-builder/src/routes/ressources/lists/value_create.tsx b/packages/app-builder/src/routes/ressources+/lists+/value_create.tsx similarity index 94% rename from packages/app-builder/src/routes/ressources/lists/value_create.tsx rename to packages/app-builder/src/routes/ressources+/lists+/value_create.tsx index df1c6b7cf..247ea32cb 100644 --- a/packages/app-builder/src/routes/ressources/lists/value_create.tsx +++ b/packages/app-builder/src/routes/ressources+/lists+/value_create.tsx @@ -7,8 +7,9 @@ import { } from '@app-builder/components/Form'; import { serverServices } from '@app-builder/services/init.server'; import { parseFormSafe } from '@app-builder/utils/input-validation'; +import { getRoute } from '@app-builder/utils/routes'; import { zodResolver } from '@hookform/resolvers/zod'; -import { type ActionArgs, json } from '@remix-run/node'; +import { type ActionFunctionArgs, json } from '@remix-run/node'; import { useFetcher } from '@remix-run/react'; import { type Namespace } from 'i18next'; import { useEffect, useState } from 'react'; @@ -27,7 +28,7 @@ const addValueFormSchema = z.object({ value: z.string().nonempty(), }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const { authService } = serverServices; const { apiClient } = await authService.isAuthenticated(request, { failureRedirect: '/login', @@ -89,7 +90,7 @@ export function NewListValue({ listId }: { listId: string }) { onSubmit={({ formData }) => { fetcher.submit(formData, { method: 'POST', - action: '/ressources/lists/value_create', + action: getRoute('/ressources/lists/value_create'), }); }} > diff --git a/packages/app-builder/src/routes/ressources/lists/value_delete.tsx b/packages/app-builder/src/routes/ressources+/lists+/value_delete.tsx similarity index 91% rename from packages/app-builder/src/routes/ressources/lists/value_delete.tsx rename to packages/app-builder/src/routes/ressources+/lists+/value_delete.tsx index a007c1c15..41f42028e 100644 --- a/packages/app-builder/src/routes/ressources/lists/value_delete.tsx +++ b/packages/app-builder/src/routes/ressources+/lists+/value_delete.tsx @@ -1,6 +1,7 @@ import { serverServices } from '@app-builder/services/init.server'; import { parseFormSafe } from '@app-builder/utils/input-validation'; -import { type ActionArgs, json } from '@remix-run/node'; +import { getRoute } from '@app-builder/utils/routes'; +import { type ActionFunctionArgs, json } from '@remix-run/node'; import { useFetcher } from '@remix-run/react'; import { type Namespace } from 'i18next'; import { useEffect, useState } from 'react'; @@ -18,7 +19,7 @@ const deleteValueFormSchema = z.object({ listValueId: z.string().uuid(), }); -export async function action({ request }: ActionArgs) { +export async function action({ request }: ActionFunctionArgs) { const { authService } = serverServices; const { apiClient } = await authService.isAuthenticated(request, { failureRedirect: '/login', @@ -66,7 +67,10 @@ export function DeleteListValue({ {children} - +
    diff --git a/packages/app-builder/src/routes/ressources/scenarios/$scenarioId/$iterationId/create_draft.tsx b/packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/create_draft.tsx similarity index 88% rename from packages/app-builder/src/routes/ressources/scenarios/$scenarioId/$iterationId/create_draft.tsx rename to packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/create_draft.tsx index b223c7f27..62b40f595 100644 --- a/packages/app-builder/src/routes/ressources/scenarios/$scenarioId/$iterationId/create_draft.tsx +++ b/packages/app-builder/src/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/create_draft.tsx @@ -2,7 +2,7 @@ import { serverServices } from '@app-builder/services/init.server'; import { parseFormSafe } from '@app-builder/utils/input-validation'; import { getRoute } from '@app-builder/utils/routes'; import { fromParams, fromUUID } from '@app-builder/utils/short-uuid'; -import { type ActionArgs, json, redirect } from '@remix-run/node'; +import { type ActionFunctionArgs, json, redirect } from '@remix-run/node'; import { useFetcher, useNavigate } from '@remix-run/react'; import { type Namespace } from 'i18next'; import { useTranslation } from 'react-i18next'; @@ -18,7 +18,7 @@ const createDraftIterationFormSchema = z.object({ iterationId: z.string().uuid(), }); -export async function action({ request, params }: ActionArgs) { +export async function action({ request, params }: ActionFunctionArgs) { const { authService } = serverServices; const { apiClient } = await authService.isAuthenticated(request, { failureRedirect: '/login', @@ -96,9 +96,13 @@ const NewDraftButton = ({ return (