-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: admin react-router migration #7922
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughThis pull request migrates the admin application from Next.js to React Router with Vite, introduces server-side rendering via Express, updates Docker deployment to nginx-based static serving, refactors layout files, and adds Next.js API compatibility shims for seamless code migration across the monorepo. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Server as Express Server
participant Vite as Vite (Dev)
participant RR as React Router
participant API as Backend API
rect rgb(220, 230, 255)
Note over Client,API: Next.js Architecture (Previous)
Client->>Server: Request /admin/page
Server->>Server: Next.js render (SSR+CSR)
Server-->>Client: HTML + JS
end
rect rgb(230, 255, 230)
Note over Client,API: React Router + Express Architecture (New)
Client->>Server: Request /admin/general
alt Development Mode
Server->>Vite: Delegate to Vite middleware
Vite->>RR: Load virtual:react-router/server-build
RR->>RR: Render route component
Vite-->>Server: HTML
else Production Mode
Server->>RR: Handle via pre-built React Router
RR->>RR: Render route component
RR-->>Server: HTML
end
Server->>API: /api//* requests
API-->>Server: API response
Server-->>Client: HTML + SSR'd content
Client->>Client: Hydrate with React
Client->>RR: Client-side navigation
RR-->>Client: Route component
</mermaid>
## Estimated code review effort
🎯 5 (Critical) | ⏱️ ~120+ minutes
This PR represents a comprehensive framework migration from Next.js to React Router + Vite for the admin application, affecting: (1) core application architecture and routing paradigm (~50 page/layout files modified), (2) build system and server infrastructure (Dockerfile, vite.config, react-router.config, Express server), (3) compatibility layer implementation (~4 new shim files), (4) widespread asset path migrations, (5) TypeScript declarations and type system updates, and (6) multi-package dependency bumps across the monorepo. The heterogeneity of changes—spanning framework-level rewrites, configuration shifts, server-side code introduction, and package.json revisions—demands careful reasoning for each file cohort, with particular attention to route metadata handling, SSR/CSR transitions, and API proxy routing.
## Possibly related PRs
- **#7593**: Primary migration PR converting the admin app from Next.js to React Router + Vite, with overlapping changes to Dockerfile, build scripts, package.json dependencies, and workspace configuration.
## Poem
> 🐰 *From Next's metadatas to Routes so fine,*
> *React Router's paths now perfectly align!*
> *Vite builds swift, Express serves the way,*
> *Compatibility shims save the day!*
> *The admin app hops to a new brigade,* ✨
<!-- walkthrough_end -->
<!-- pre_merge_checks_walkthrough_start -->
## Pre-merge checks and finishing touches
<details>
<summary>❌ Failed checks (1 warning)</summary>
| Check name | Status | Explanation | Resolution |
| :----------------: | :--------- | :------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------- |
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. | You can run `@coderabbitai generate docstrings` to improve docstring coverage. |
</details>
<details>
<summary>✅ Passed checks (2 passed)</summary>
| Check name | Status | Explanation |
| :---------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Title Check | ✅ Passed | The pull request title "feat: admin react-router migration" clearly and concisely summarizes the primary change across the entire changeset. The raw_summary demonstrates that the core objective involves a comprehensive migration of the admin application from a Next.js architecture to React Router combined with Vite, including removal of Next.js configuration, addition of React Router routing infrastructure, Vite build configuration, Express server setup, and multiple compatibility shims. The title directly reflects this main undertaking and would allow teammates scanning commit history to quickly understand that this changeset involves the React Router migration of the admin application. |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
</details>
<!-- pre_merge_checks_walkthrough_end -->
<!-- finishing_touch_checkbox_start -->
<details>
<summary>✨ Finishing touches</summary>
- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate docstrings
<details>
<summary>🧪 Generate unit tests (beta)</summary>
- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Post copyable unit tests in a comment
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `feat/admin-react-router-migration`
</details>
</details>
<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->
---
Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=makeplane/plane&utm_content=7922)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
<details>
<summary>❤️ Share</summary>
- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)
</details>
<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>
<!-- tips_end -->
<!-- internal state start -->
<!-- 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= -->
<!-- internal state end -->
|
5fd5879 to
95870bc
Compare
| @@ -0,0 +1,25 @@ | |||
| module.exports = { | |||
| root: true, | |||
| extends: ["@plane/eslint-config/next.js"], | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sriramveeraghanta we should probably make an ESLint for react-router, but I figure we can do a fast follow.
95870bc to
959434a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR migrates the admin app from Next.js to React Router, updating the build system, routing structure, and deployment configuration while maintaining the same functionality.
- Replaces Next.js with React Router + Vite for build tooling and routing
- Updates package configurations to use specific TypeScript versions instead of catalog references
- Migrates from SSR/static site generation to client-side routing with optional SSR support
Reviewed Changes
Copilot reviewed 114 out of 143 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| apps/admin/package.json | Updates dependencies from Next.js to React Router, adds Vite build tooling |
| apps/admin/vite.config.ts | New Vite configuration with React Router plugin and Next.js compatibility aliases |
| apps/admin/react-router.config.ts | React Router configuration for client-side routing |
| apps/admin/app/root.tsx | New root layout component replacing Next.js app structure |
| apps/admin/app/routes.ts | Manual route configuration for React Router |
| apps/admin/app/compat/next/* | Compatibility shims for Next.js APIs (navigation, Link, Image) |
| apps/admin/Dockerfile.admin | Updates Docker deployment from Node.js server to nginx static hosting |
| packages/*/package.json | Replaces catalog TypeScript references with explicit version 5.8.3 |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/admin/Dockerfile.admin (1)
40-57: Rename NEXT_PUBLIC_ env vars to VITE_ and update references**
Verified these vars are still used in apps/admin:
- vite.config.ts (PUBLIC_ENV_KEYS)
- react-router.config.ts (basename)
- server/app.ts (proxy targets & base paths)
Either rename to VITE_* and adjust PUBLIC_ENV_KEYS, process.env references, etc., or explicitly configure Vite to expose NEXT_PUBLIC_*—but align with Vite conventions.
♻️ Duplicate comments (2)
apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx (1)
22-22: React namespace not available forReact.FCusage.The component type annotation uses
React.FC<Props>, butReactis not imported. This is the same issue flagged in the import statement comment—apply the fix at Line 1 to resolve this.apps/admin/.eslintrc.cjs (1)
3-3: Consider updating to a React Router-specific ESLint config.The config extends
"@plane/eslint-config/next.js"which may contain Next.js-specific rules that are no longer applicable after migrating to React Router. While this might work for now, it could lead to false positives or missed issues specific to React Router patterns.As noted in the previous review comment, consider creating or using a React Router-specific ESLint configuration to ensure linting rules align with the new framework.
🧹 Nitpick comments (14)
apps/admin/core/utils/public-asset.ts (1)
1-1: Remove the empty module placeholder.This file exports nothing and only serves to toggle module mode. Unless the build requires a marker file here, better to drop it to avoid dead artifacts. If a stub is needed, add a comment explaining why.
apps/admin/core/components/common/empty-state.tsx (1)
12-12: Consider tightening the icon type for consistency.Line 10's
imageprop was changed fromanytostring. For consistency with this type-tightening pattern, consider also specifying a more precise type foricon(e.g.,React.ReactNodeor a more specific icon type if applicable).- icon?: any; + icon?: React.ReactNode;apps/admin/app/(all)/(dashboard)/general/intercom.tsx (2)
72-72: Simplify redundant boolean conversion.The ternary
isIntercomEnabled ? true : falseis redundant becauseisIntercomEnabledis already a boolean value (derived from lines 24-30).Apply this diff to simplify:
- value={isIntercomEnabled ? true : false} + value={isIntercomEnabled}
1-1: Consider removing Next.js-specific directive.The
"use client"directive is a Next.js 13+ convention for marking client components. Since this PR migrates from Next.js to React Router, this directive may no longer be necessary and could be removed.Apply this diff if you want to remove it:
-"use client"; - import { useState } from "react";apps/admin/app/compat/next/navigation.ts (1)
17-19: Consider router-level prefetching instead of a no-op.The
prefetchmethod is currently a no-op, which means eager link prefetching patterns from Next.js won't improve perceived performance. React Router v7 supports route-level prefetching via<Link prefetch>and programmatic prefetching.Consider implementing basic prefetch logic if performance optimization is important, or document that this feature is intentionally omitted.
.dockerignore (1)
64-69: Consider removing duplicate patterns.Lines 67-68 duplicate existing patterns:
build/already covered by L13-14 (build/and**/build/)node_modules/already covered by L6-7 (node_modules/and**/node_modules/)While Docker handles duplicates gracefully, removing them would reduce redundancy and improve maintainability.
Apply this diff to remove duplicates:
# React Router - https://github.com/remix-run/react-router-templates/blob/dc79b1a065f59f3bfd840d4ef75cc27689b611e6/default/.dockerignore .react-router/ -build/ -node_modules/ README.mdapps/admin/react-router.config.ts (1)
1-8: Consider renaming environment variable prefix.The configuration correctly sets
ssr: falsewhich aligns with the nginx-based static serving in the Dockerfile. However,NEXT_PUBLIC_ADMIN_BASE_PATHretains the Next.js naming convention. Consider renaming environment variables to follow Vite conventions (e.g.,VITE_ADMIN_BASE_PATH) for consistency with the new stack, unless maintaining Next.js prefixes is intentional for backward compatibility.If renaming, update this line and corresponding references:
- basename: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH, + basename: process.env.VITE_ADMIN_BASE_PATH,apps/admin/server/app.ts (2)
27-36: Add error handling for the proxy middleware.The proxy middleware lacks error event handlers. If the upstream API is unreachable or times out, this could result in unhandled errors or hanging requests.
Consider adding error handlers:
app.use( "/api", createProxyMiddleware({ target: NEXT_PUBLIC_API_BASE_URL, changeOrigin: true, secure: false, + onError: (err, req, res) => { + console.error('Proxy error:', err); + res.status(502).json({ error: 'Bad Gateway' }); + }, pathRewrite: (path: string) => NORMALIZED_API_BASE_PATH === "/api" ? path : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH), }) );
7-18: Consider validating required environment variables.The environment variable normalization provides defaults, but it might be beneficial to validate that critical variables like
NEXT_PUBLIC_API_BASE_URLare set in production to avoid fallback to localhost.Add validation for production:
if (process.env.NODE_ENV === 'production' && !process.env.NEXT_PUBLIC_API_BASE_URL) { console.warn('Warning: NEXT_PUBLIC_API_BASE_URL not set in production, using default'); }apps/admin/deploy/nginx.default.conf.template (2)
25-27: Avoidifin location context when possible.Using
ifdirectives in Nginx location blocks is generally discouraged and can lead to unexpected behavior. Consider using amapdirective or separate location blocks instead.Replace the conditional rewrite with a map:
# Add this at the http level in nginx.conf or at the top of the server block map $api_base $rewrite_path { default $uri; "~^(?!/api).*" $api_base$uri; } # Then in the location block: location /api/ { set $api_origin $NEXT_PUBLIC_API_BASE_URL; set $api_base $NEXT_PUBLIC_API_BASE_PATH; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; rewrite ^/api/(.*)$ $api_base/$1 break; proxy_pass $api_origin; }However, if the current approach works reliably in your setup, you may choose to defer this refactor.
11-12: Consider adding caching and compression for static assets.Static assets should be cached to improve performance, and gzip/brotli compression should be enabled for text-based assets.
Add caching and compression directives:
# Add gzip compression at the top of the server block gzip on; gzip_vary on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; gzip_min_length 1000; # Update the static asset location with caching location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { root /usr/share/nginx/html; expires 1y; add_header Cache-Control "public, immutable"; access_log off; }Also applies to: 43-45
apps/admin/app/root.tsx (1)
55-57: Consider adding an error boundary.The root component lacks error handling. If a child route throws an uncaught error, users will see a broken page without a fallback UI.
React Router supports error boundaries through route configuration. Consider adding an
ErrorBoundarycomponent to handle route-level errors:import { useRouteError, isRouteErrorResponse } from "react-router"; export function ErrorBoundary() { const error = useRouteError(); if (isRouteErrorResponse(error)) { return ( <div className="flex h-screen items-center justify-center"> <div className="text-center"> <h1 className="text-4xl font-bold">{error.status}</h1> <p className="text-lg">{error.statusText}</p> </div> </div> ); } return ( <div className="flex h-screen items-center justify-center"> <div className="text-center"> <h1 className="text-4xl font-bold">Oops!</h1> <p className="text-lg">Something went wrong</p> </div> </div> ); }Then export it alongside the Root component to be used in route configuration.
apps/admin/package.json (2)
35-35: Remove unusedclient-onlydependency
Theclient-onlypackage declared at apps/admin/package.json:35 isn’t imported or required anywhere—no SSR conflict. Consider removing it if it’s not needed.
77-79: Confirm TS 5.8.3, Vite 7.1.7 & React Router 7.9.x integration
- Ensure your Node.js version meets Vite 7’s minimum requirement.
- Convert React Router route-generated type imports to
import type { … }per framework-mode docs.- Run your dev/build and type-check pipeline (including any Vite plugins like vite-plugin-checker) after upgrading to TS 5.8.3/Vite 7.1.7.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (21)
apps/admin/app/assets/favicon/apple-touch-icon.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-16x16.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-32x32.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon.icois excluded by!**/*.icoapps/admin/app/assets/images/logo-spinner-dark.gifis excluded by!**/*.gifapps/admin/app/assets/images/logo-spinner-light.gifis excluded by!**/*.gifapps/admin/app/assets/images/plane-takeoff.pngis excluded by!**/*.pngapps/admin/app/assets/instance/instance-failure-dark.svgis excluded by!**/*.svgapps/admin/app/assets/instance/instance-failure.svgis excluded by!**/*.svgapps/admin/app/assets/logos/github-black.pngis excluded by!**/*.pngapps/admin/app/assets/logos/github-white.pngis excluded by!**/*.pngapps/admin/app/assets/logos/gitlab-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/google-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/oidc-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/saml-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-dark.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-light.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern-dark.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern.svgis excluded by!**/*.svgapps/admin/public/instance/plane-takeoff.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (107)
.dockerignore(1 hunks).gitignore(1 hunks)apps/admin/.dockerignore(1 hunks)apps/admin/.eslintrc.cjs(1 hunks)apps/admin/.eslintrc.js(0 hunks)apps/admin/Dockerfile.admin(1 hunks)apps/admin/app/(all)/(dashboard)/ai/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/ai/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/ai/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/email/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx(4 hunks)apps/admin/app/(all)/(dashboard)/general/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/intercom.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/general/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/header.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/image/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/layout.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx(1 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/workspace/page.tsx(2 hunks)apps/admin/app/(all)/(home)/auth-banner.tsx(1 hunks)apps/admin/app/(all)/(home)/auth-helpers.tsx(2 hunks)apps/admin/app/(all)/(home)/layout.tsx(1 hunks)apps/admin/app/(all)/(home)/page.tsx(2 hunks)apps/admin/app/(all)/(home)/sign-in-form.tsx(2 hunks)apps/admin/app/(all)/instance.provider.tsx(1 hunks)apps/admin/app/(all)/store.provider.tsx(2 hunks)apps/admin/app/(all)/user.provider.tsx(1 hunks)apps/admin/app/compat/next/image.tsx(1 hunks)apps/admin/app/compat/next/link.tsx(1 hunks)apps/admin/app/compat/next/navigation.ts(1 hunks)apps/admin/app/layout.tsx(0 hunks)apps/admin/app/providers.tsx(1 hunks)apps/admin/app/root.tsx(1 hunks)apps/admin/app/routes.ts(1 hunks)apps/admin/app/types/next-image.d.ts(1 hunks)apps/admin/app/types/next-link.d.ts(1 hunks)apps/admin/app/types/next-navigation.d.ts(1 hunks)apps/admin/app/types/react-router-virtual.d.ts(1 hunks)apps/admin/ce/components/authentication/authentication-modes.tsx(2 hunks)apps/admin/core/components/authentication/authentication-method-card.tsx(1 hunks)apps/admin/core/components/authentication/email-config-switch.tsx(1 hunks)apps/admin/core/components/authentication/github-config.tsx(1 hunks)apps/admin/core/components/authentication/gitlab-config.tsx(1 hunks)apps/admin/core/components/authentication/google-config.tsx(1 hunks)apps/admin/core/components/authentication/password-config-switch.tsx(1 hunks)apps/admin/core/components/common/banner.tsx(1 hunks)apps/admin/core/components/common/controller-input.tsx(1 hunks)apps/admin/core/components/common/empty-state.tsx(1 hunks)apps/admin/core/components/common/logo-spinner.tsx(1 hunks)apps/admin/core/components/instance/failure.tsx(1 hunks)apps/admin/core/components/instance/instance-not-ready.tsx(1 hunks)apps/admin/core/components/instance/loading.tsx(1 hunks)apps/admin/core/components/instance/setup-form.tsx(2 hunks)apps/admin/core/components/new-user-popup.tsx(1 hunks)apps/admin/core/hooks/store/use-instance.tsx(1 hunks)apps/admin/core/hooks/store/use-theme.tsx(1 hunks)apps/admin/core/hooks/store/use-user.tsx(1 hunks)apps/admin/core/hooks/store/use-workspace.tsx(1 hunks)apps/admin/core/store/instance.store.ts(2 hunks)apps/admin/core/store/root.store.ts(1 hunks)apps/admin/core/store/theme.store.ts(1 hunks)apps/admin/core/store/user.store.ts(1 hunks)apps/admin/core/store/workspace.store.ts(1 hunks)apps/admin/core/utils/public-asset.ts(1 hunks)apps/admin/deploy/nginx.default.conf.template(1 hunks)apps/admin/next-env.d.ts(0 hunks)apps/admin/next.config.js(0 hunks)apps/admin/package.json(2 hunks)apps/admin/public/.well-known/appspecific/com.chrome.devtools.json(1 hunks)apps/admin/public/favicon/site.webmanifest(0 hunks)apps/admin/react-router.config.ts(1 hunks)apps/admin/server.mjs(1 hunks)apps/admin/server/app.ts(1 hunks)apps/admin/styles/globals.css(1 hunks)apps/admin/tsconfig.json(1 hunks)apps/admin/vite.config.ts(1 hunks)apps/live/package.json(1 hunks)apps/space/package.json(2 hunks)apps/web/app/(all)/layout.tsx(1 hunks)apps/web/package.json(2 hunks)packages/constants/package.json(1 hunks)packages/decorators/package.json(1 hunks)packages/editor/package.json(2 hunks)packages/editor/tsdown.config.ts(1 hunks)packages/eslint-config/package.json(1 hunks)packages/hooks/package.json(1 hunks)packages/i18n/package.json(1 hunks)
⛔ Files not processed due to max files limit (13)
- packages/logger/package.json
- packages/propel/package.json
- packages/propel/src/empty-state/assets/vertical-stack/cycle.tsx
- packages/propel/src/empty-state/assets/vertical-stack/project.tsx
- packages/propel/src/icons/brand/plane-lockup.tsx
- packages/propel/tsdown.config.ts
- packages/services/package.json
- packages/shared-state/package.json
- packages/types/package.json
- packages/ui/package.json
- packages/utils/package.json
- packages/utils/src/string.ts
- pnpm-workspace.yaml
💤 Files with no reviewable changes (14)
- apps/admin/app/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx
- apps/admin/app/(all)/(dashboard)/ai/layout.tsx
- apps/admin/app/(all)/(dashboard)/general/layout.tsx
- apps/admin/.eslintrc.js
- apps/admin/app/(all)/(dashboard)/workspace/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx
- apps/admin/app/(all)/(dashboard)/email/layout.tsx
- apps/admin/next-env.d.ts
- apps/admin/app/(all)/(dashboard)/image/layout.tsx
- apps/admin/public/favicon/site.webmanifest
- apps/admin/next.config.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-02T08:14:49.260Z
Learnt from: sriramveeraghanta
PR: makeplane/plane#7697
File: apps/web/app/(all)/[workspaceSlug]/(projects)/header.tsx:12-13
Timestamp: 2025-09-02T08:14:49.260Z
Learning: The star-us-link.tsx file in apps/web/app/(all)/[workspaceSlug]/(projects)/ already has "use client" directive at the top, making it a proper Client Component for hook usage.
Applied to files:
apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
🧬 Code graph analysis (10)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (3)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)
apps/admin/app/(all)/(dashboard)/email/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/types/next-navigation.d.ts (1)
apps/admin/app/compat/next/navigation.ts (2)
usePathname(25-28)useSearchParams(30-33)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/(all)/(home)/auth-banner.tsx (1)
packages/constants/src/auth.ts (1)
TAdminAuthErrorInfo(89-94)
apps/admin/app/root.tsx (1)
apps/admin/app/providers.tsx (1)
AppProviders(19-32)
apps/admin/app/(all)/(dashboard)/authentication/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (3)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)
apps/admin/core/components/new-user-popup.tsx (1)
apps/admin/core/hooks/store/use-theme.tsx (1)
useTheme(6-10)
apps/admin/app/(all)/(dashboard)/workspace/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)
🔇 Additional comments (104)
apps/admin/styles/globals.css (1)
1-1: Font import update looks goodAdding the
.cssextension keeps the stylesheet resolvable under the new Vite/React Router build pipeline. Nice alignment with the asset changes elsewhere.apps/admin/public/.well-known/appspecific/com.chrome.devtools.json (1)
1-1: Confirm Chrome devtools well-known payload requirements.Is Chrome satisfied with an empty
{}here, or does it expect specific fields (e.g., a version or configuration block)? Please confirm against the Chrome DevTools well-known spec before shipping.apps/admin/core/store/workspace.store.ts (1)
5-7: LGTM! Type-only imports improve bundle efficiency.The refactor correctly converts interface and type imports to
import type, which removes runtime dependencies while preserving type checking. This follows TypeScript best practices and can improve tree-shaking.apps/admin/core/hooks/store/use-workspace.tsx (1)
4-4: LGTM! Consistent with the type-only import pattern.The change correctly applies the type-only import refactor to the hook layer, maintaining consistency with the store modules.
apps/admin/core/store/user.store.ts (1)
3-8: LGTM! Correctly distinguishes runtime values from types.The refactor properly splits imports:
EUserStatusremains a runtime import (used at lines 81, 86)TUserStatus,IUser, andCoreRootStorebecome type-only importsThis demonstrates careful analysis of runtime vs. compile-time dependencies.
apps/admin/core/hooks/store/use-user.tsx (1)
4-4: LGTM! Type-only import applied correctly.apps/admin/core/store/instance.store.ts (1)
4-16: LGTM! Proper separation of runtime values and types.The refactor correctly:
- Preserves
EInstanceStatusas a runtime import (used at line 116)- Converts
TInstanceStatusand all interface imports to type-only imports- Maintains the same careful runtime vs. type distinction seen across the PR
apps/admin/core/hooks/store/use-instance.tsx (1)
4-4: LGTM! Type-only import applied correctly.apps/admin/core/store/theme.store.ts (1)
3-3: LGTM! Type-only import applied correctly.apps/admin/core/hooks/store/use-theme.tsx (1)
4-4: LGTM! Type-only import applied correctly.apps/admin/core/store/root.store.ts (1)
3-10: LGTM! Excellent TypeScript best practice.Splitting value imports and type-only imports improves tree-shaking and makes the distinction between compile-time and runtime imports explicit. This pattern ensures interfaces are erased at compile time and don't bloat the bundle.
apps/web/app/(all)/layout.tsx (1)
8-8: LGTM – CSS import is valid and consistent.
The path@plane/propel/styles/react-day-picker.csscorrectly resolves via package exports and aligns with other stylesheet imports.apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx (1)
64-80: LGTM! Consistent Fragment usage.The refactor from
React.Fragmentto the importedFragmentis correct and improves consistency. All three usages (lines 64, 67, 80) are properly updated.apps/admin/core/components/common/empty-state.tsx (2)
10-10: LGTM! Type improvement enhances type safety.The change from
anytostringaligns with the PR's type-tightening pattern and provides better type safety for the image prop.
1-1: Next.js imports covered by compat layer
The"use client"directive andnext/*imports are forwarded by shims inapps/admin/app/compat/next—no changes required.apps/admin/core/components/new-user-popup.tsx (2)
11-12: LGTM! Vite asset imports correctly configured.The
?urlsuffix is the correct Vite syntax for importing asset URLs. This will work properly with the Vite build system.
16-16: LGTM! Export signature aligns with modern React patterns.Removing the explicit
React.FCtype annotation in favor of type inference is a widely adopted best practice that reduces boilerplate while maintaining type safety through theobserverHOC.apps/admin/app/(all)/(dashboard)/general/intercom.tsx (1)
7-7: LGTM! Type-only import is best practice.Converting to a type-only import for
IFormattedInstanceConfigurationis appropriate since it's only used as a type annotation on line 38. This ensures the import is erased during compilation.apps/admin/app/(all)/(home)/auth-helpers.tsx (3)
5-7: LGTM!Using
import typefor type-only imports is a best practice that improves tree-shaking and makes the intent clear.
30-30: LGTM!Using
React.ReactNodeis more explicit and eliminates the need for a separateReactNodeimport.
10-13: Asset paths confirmed
All imported logos exist at the specified paths—URL imports are correct.apps/web/package.json (2)
63-63: Node engine requirement satisfied Theengines.nodefield is set to>=22.18.0, which meets[email protected]’s Node ≥18.17.0 requirement.
80-80: Align root TypeScript version. All workspace packages pin TS to 5.8.3; root package.json lists"typescript": "catalog:"(line 35). Confirm this is intentional or update it to 5.8.3.packages/eslint-config/package.json (1)
12-21: [email protected] is published – ready to mergepackages/editor/tsdown.config.ts (1)
11-11: LGTM! Export alias aligns with package.json.Adding the "./styles.css" alias alongside "./styles" provides flexibility for consumers to import styles using either path convention. The configuration correctly maps both to the same destination file.
packages/editor/package.json (2)
21-22: LGTM! Export aliases provide import flexibility.The addition of both "./styles" and "./styles.css" export paths pointing to the same CSS file allows consumers to use either import convention. The trailing comma on line 21 follows best practices for JSON formatting.
90-90: No action required: TypeScript 5.8.3 is a valid published version on npm.apps/admin/app/types/next-image.d.ts (1)
1-5: LGTM! Type declaration aligns with the runtime shim.The ambient module declaration correctly defines the minimal type surface for the Next.js Image compatibility shim. The Props type appropriately extends native img element props with a required src field, matching the implementation in
apps/admin/app/compat/next/image.tsx.apps/admin/app/compat/next/link.tsx (1)
1-23: LGTM! Clean Next.js Link adapter for react-router.The compatibility shim correctly maps Next.js Link props to react-router's Link component. The explicit destructuring and prefixing of ignored Next.js-specific props (
prefetch,scroll,shallow) with underscores clearly documents the intentional omission of these features in the migration.apps/admin/app/compat/next/image.tsx (1)
1-14: LGTM! Minimal image shim as documented.The implementation correctly provides a drop-in replacement for Next.js Image imports using a native img element. The default
alt=""prevents accessibility errors, though developers should still provide meaningful alt text for non-decorative images.Note: This shim intentionally omits Next.js image optimization features (lazy loading, srcset, blur placeholders, etc.). Ensure this trade-off is acceptable for your use case.
apps/admin/app/compat/next/navigation.ts (2)
6-23: LGTM! Navigation hooks correctly adapted to react-router.The
useRouter,usePathname, anduseSearchParamshooks properly map Next.js navigation patterns to react-router equivalents. The memoization of the router object with[navigate]dependency is correct and prevents unnecessary re-renders.
14-16: Verify full page reload inrefresh()
The implementation useslocation.reload(), causing a full-page reload that resets scroll position and client state. Confirm this hard-reload behavior is intentional and acceptable for all uses ofrouter.refresh().apps/admin/app/types/next-navigation.d.ts (1)
1-13: LGTM! Type declarations match the runtime implementation.The ambient module declaration correctly types the navigation hooks, including the flexible
prefetchsignature that supports both synchronous and asynchronous implementations. All types align with the runtime shim inapps/admin/app/compat/next/navigation.ts.apps/admin/app/types/next-link.d.ts (1)
1-12: LGTM! Type declaration supports the Link compatibility layer.The ambient module declaration accurately defines the Next.js Link API surface, including both standard anchor props and Next.js-specific options. This aligns with the runtime implementation in
apps/admin/app/compat/next/link.tsxand enables type-safe usage throughout the codebase.apps/admin/core/components/instance/loading.tsx (3)
4-5: LGTM! Asset imports correctly use Vite convention.The asset imports correctly use the
?urlsuffix, which is Vite's convention for explicitly importing the URL string of static assets. The path change frompublic/assetstoapp/assetsaligns with the React Router + Vite migration strategy.
7-17: Compatibility shim confirmed—no changes needed.The Vite alias in vite.config.ts maps
next/imagetoapp/compat/next/image.tsx, whoseImageshim forwardssrc,alt, andclassNameto a native<img>. The theme-based spinner will render correctly.
1-2: Verify Next.js shim resolution and next-themes compatibility
- Confirm your Vite/RR path alias or tsconfig maps
next/image→apps/admin/app/compat/next/image.tsxand that the shim supports all props you’re using (src, width, height, etc.).- Ensure
next-themesworks as expected in the Vite + React Router admin app (no hidden Next.js SSR/router dependencies)..gitignore (1)
104-106: LGTM!The new ignore patterns appropriately exclude React Router-generated artifacts and build outputs from version control, aligning with the PR's migration to React Router + Vite.
apps/admin/.dockerignore (1)
1-5: LGTM!The new
.dockerignorefile appropriately excludes build artifacts and development files from the admin app's Docker context, consistent with React Router best practices.apps/admin/core/components/authentication/email-config-switch.tsx (1)
6-6: LGTM!Converting to a type-only import is appropriate since
TInstanceAuthenticationMethodKeysis used exclusively for type annotations. This improves tree-shaking and aligns with TypeScript best practices.apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx (1)
10-10: LGTM!The type-only import is correct since
IWorkspaceis used exclusively for type annotations (lines 24, 38, 42). This optimization improves tree-shaking.apps/admin/core/components/instance/setup-form.tsx (2)
3-3: LGTM!Removing
FCfrom imports is appropriate since the component now usesReact.FCdirectly (line 56), which is equivalent and aligns with the PR's consistent typing pattern.
56-56: LGTM!The migration from
FCtoReact.FCmaintains type safety while aligning with the PR's systematic typing refactor across admin components.apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx (2)
3-3: LGTM!Removing
FCfrom imports aligns with the component's use ofReact.FCon line 37, maintaining consistency with the PR's typing refactor.
37-37: LGTM!The migration to
React.FCis consistent with the broader typing updates across admin components and works correctly with theobserverwrapper.apps/admin/app/(all)/(dashboard)/sidebar.tsx (2)
3-3: LGTM!Removing
FCfrom imports is appropriate since the component (line 14) no longer uses an explicit FC type annotation, relying instead on type inference through theobserverwrapper.
14-14: LGTM!Removing the explicit
FCtype annotation simplifies the code while maintaining type safety through TypeScript's inference of the component type from theobserverwrapper. This is a common and valid pattern for MobX-React components.apps/admin/core/components/authentication/authentication-method-card.tsx (1)
16-16: LGTM!The typing change from
FC<Props>toReact.FC<Props>is consistent with the broader refactor across the admin app.apps/admin/app/(all)/instance.provider.tsx (1)
6-6: LGTM!Using
observer<React.FC<React.PropsWithChildren>>is a cleaner, more idiomatic approach than defining a separate props interface for a simple children-only component.apps/admin/app/(all)/(dashboard)/general/form.tsx (2)
8-8: LGTM!The type-only import is appropriate here and can improve build performance by allowing the TypeScript compiler to elide these imports at compile time.
22-22: LGTM!The typing change from
FC<IGeneralConfigurationForm>toReact.FC<IGeneralConfigurationForm>is consistent with the broader refactor.apps/admin/core/components/instance/instance-not-ready.tsx (3)
7-7: LGTM!The
?urlquery parameter is the correct Vite pattern for importing static assets as URLs. This change aligns with the migration from Next.js to Vite.
9-9: LGTM!The typing change from
FCtoReact.FCis consistent with the broader refactor.
3-4: Next.js component imports are intentionally shimmed in admin. Admin’s Vite config (apps/admin/vite.config.tsaliasingnext/imageandnext/linktoapp/compat/next/...) provides compatibility, so no migration is required.apps/admin/tsconfig.json (3)
2-2: LGTM!Switching from the Next.js preset to the React Router preset correctly aligns the TypeScript configuration with the migration.
5-6: LGTM!Adding
rootDirsfor React Router types and including Node and Vite client types properly supports the new build tooling.
15-15: LGTM!The updated
includepatterns correctly support React Router's type generation (.react-router/types/**/*) and cover all necessary source files.apps/admin/app/(all)/(home)/auth-banner.tsx (2)
3-3: LGTM!The type-only import is appropriate and improves build performance.
10-10: LGTM!The typing change from
FC<TAuthBanner>toReact.FC<TAuthBanner>is consistent with the broader refactor.apps/admin/app/(all)/store.provider.tsx (1)
26-26: LGTM!Using
React.ReactNodeinstead of importingReactNodeseparately makes the typing more explicit and consistent.apps/admin/app/(all)/(dashboard)/general/page.tsx (2)
6-6: LGTM!The type import from
./+types/pagefollows React Router's type-safe route conventions.
32-32: LGTM!The
Route.MetaFunctionexport correctly implements React Router's metadata pattern, replacing Next.js's metadata API. The returned array with a title object is the correct format.apps/admin/core/components/instance/failure.tsx (1)
8-9: LGTM!The asset imports using the
?urlsuffix are correct for Vite's asset handling and will resolve to URL strings.apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx (1)
8-8: LGTM!The type-only imports for
IFormattedInstanceConfiguration,TInstanceEmailConfigurationKeys, andTControllerInputFormFieldare correct and improve tree-shaking by ensuring these types don't appear in the runtime bundle.Also applies to: 12-13
apps/admin/app/(all)/(dashboard)/ai/form.tsx (1)
6-6: LGTM!The type-only imports are correctly applied and will improve bundle size by excluding these types from the runtime.
Also applies to: 8-9
apps/admin/app/(all)/(dashboard)/image/form.tsx (1)
5-5: LGTM!The type-only imports are correctly applied for better tree-shaking.
apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
95-96: LGTM!The
metaexport provides proper route metadata and follows the React Router v7 pattern consistently used across other page components.apps/admin/app/(all)/(dashboard)/ai/page.tsx (1)
46-47: LGTM!The
metaexport follows the established React Router v7 pattern for providing route metadata.apps/admin/app/(all)/(home)/sign-in-form.tsx (1)
7-8: LGTM!The split between type-only imports and value imports is correct and improves tree-shaking by ensuring type-only symbols don't appear in the runtime bundle.
apps/admin/vite.config.ts (3)
45-45: LGTM! Plugin configuration is appropriate.The React Router plugin and tsconfig-paths plugin are correctly configured. Explicitly setting the tsconfig path ensures proper resolution in monorepo environments.
48-51: LGTM! Next.js compatibility shims support gradual migration.The alias configuration for Next.js imports (
next/image,next/link,next/navigation) provides backward compatibility, allowing components to migrate incrementally without requiring immediate rewrites.
32-34: Dynamic process.env usage is limited to the Vite config
The onlyprocess.env[key]access is inapps/admin/vite.config.tswhen buildingpublicEnv; client code uses only staticprocess.env.KEYand is unaffected by thedefinereplacement.Likely an incorrect or invalid review comment.
apps/admin/app/(all)/user.provider.tsx (1)
9-9: LGTM! Typing refactor aligns with React Router migration patterns.The change from an explicit
FC<IUserProvider>interface toobserver<React.FC<React.PropsWithChildren>>simplifies the typing while maintaining type safety. This pattern is consistent across the PR's migration to React Router.apps/admin/.eslintrc.cjs (1)
9-21: LGTM! Import rules enforce type-import separation.The configured rules properly enforce the PR's pattern of separating type-only imports from value imports:
- Disables
no-duplicate-importsin favor of the more flexibleimport/no-duplicates- Enforces top-level type specifiers with
import/consistent-type-specifier-style- Prevents import side effects with
@typescript-eslint/no-import-type-side-effects- Requires separate type imports with
@typescript-eslint/consistent-type-importsThis configuration supports better tree-shaking and clearer code semantics.
apps/admin/core/components/authentication/password-config-switch.tsx (1)
6-6: LGTM! Type-only import follows project conventions.Converting
TInstanceAuthenticationMethodKeysto a type-only import is consistent with the ESLint rules configured in.eslintrc.cjsand the broader type-import discipline throughout this PR.apps/admin/core/components/authentication/github-config.tsx (1)
10-10: LGTM! Type-only import follows project conventions.Converting
TInstanceAuthenticationMethodKeysto a type-only import is consistent with the ESLint configuration and the PR-wide pattern of separating type and value imports.apps/admin/core/components/authentication/gitlab-config.tsx (1)
9-9: LGTM! Type-only import follows project conventions.Converting
TInstanceAuthenticationMethodKeysto a type-only import aligns with the ESLint configuration and the consistent type-import pattern across authentication components.apps/admin/app/(all)/(home)/page.tsx (1)
43-46: LGTM! Route metadata follows React Router v7 conventions.The
metaexport correctly implements React Router'sRoute.MetaFunctionpattern, providing page title and description metadata. This replaces Next.js's metadata API and enables proper SEO and browser tab titles in the React Router migration.apps/admin/app/(all)/(home)/layout.tsx (1)
3-10: LGTM! Layout correctly uses React Router's Outlet pattern.The migration from Next.js's
childrenprop to React Router's<Outlet />component is the correct pattern for nested routing. TheOutletwill render the matched child route, maintaining the layout wrapper around nested pages.apps/admin/app/types/react-router-virtual.d.ts (1)
1-5: LGTM! Standard virtual module declaration.The ambient module declaration for the virtual React Router server build follows standard patterns for build-time generated modules. The use of
anytype is acceptable here since the build structure is determined at build time.apps/admin/core/components/authentication/google-config.tsx (1)
9-9: LGTM! Type-only import improves bundle size.Converting
TInstanceAuthenticationMethodKeysto a type-only import is a best practice that reduces bundle size by eliminating unnecessary runtime imports for types.apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (3)
16-19: LGTM! Consistent Route typing pattern.The addition of
Route.ComponentPropstyping and the meta export aligns with the React Router migration pattern used consistently across other pages in this PR.
103-103: LGTM! Route metadata follows conventions.The meta export provides the route title using React Router's standard pattern, consistent with other authentication pages in the PR.
10-10: GitLab logo asset path verified. Verified thatapps/admin/app/assets/logos/gitlab-logo.svgexists and aligns with the import.apps/admin/core/components/common/controller-input.tsx (1)
4-5: LGTM! Proper import separation.Splitting the imports correctly keeps
Controlleras a runtime import (used in JSX) while makingControltype-only (used only in type annotations). This improves tree-shaking and reduces bundle size.apps/admin/app/(all)/(dashboard)/image/page.tsx (2)
9-12: LGTM! Consistent migration pattern.The Route typing follows the same pattern used across other pages in this PR, ensuring consistency in the React Router migration.
42-42: LGTM! Standard route metadata.The meta export provides the page title using React Router conventions, consistent with other pages.
apps/admin/app/(all)/(dashboard)/authentication/page.tsx (3)
8-8: LGTM! Type-only import reduces bundle size.Converting
TInstanceConfigurationKeysto a type-only import is appropriate and helps with tree-shaking.
15-17: LGTM! Consistent Route typing.The component typing follows the established pattern for the React Router migration across all pages in this PR.
115-115: LGTM! Route metadata follows conventions.The meta export is consistent with other pages and provides appropriate page title metadata.
apps/admin/Dockerfile.admin (1)
66-73: Port configuration verified: nginx listens on port 3000, matching the EXPOSE directive.apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (2)
10-10: LGTM! Asset import updated for Vite.The asset import path correctly uses the
?urlsuffix for Vite's asset handling, which returns the URL string rather than the default behavior.
16-16: LGTM! Route typing migration is consistent.The addition of Route type imports, typed component props using
Route.ComponentProps, and themetaexport align with the React Router migration pattern observed across other authentication pages (github, gitlab, etc.).Also applies to: 19-19, 104-104
apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx (1)
3-3: LGTM! Type import optimization follows best practices.The changes correctly:
- Use type-only imports for types (
IFormattedInstanceConfiguration,TInstanceGithubAuthenticationConfigurationKeys,TControllerInputFormField,TCopyField)- Switch to explicit
React.FC<Props>typing- Separate runtime and type imports appropriately
This aligns with TypeScript best practices for tree-shaking and type erasure.
Also applies to: 12-12, 18-21, 31-31
apps/admin/server/app.ts (2)
22-25: LGTM! Proxy configuration is correct.Enabling
trust proxyensures that Express correctly resolves the hostname and protocol when deployed behind reverse proxies like Nginx, which is essential for generating correct URLs and redirects.
33-34: pathRewrite logic is correctly scoped and safe: the regex only replaces a leading/api, and replacement uses a literal string; no other/apiproxies or hardcoded routes detected.apps/admin/app/root.tsx (2)
24-39: LGTM! Layout structure follows React Router best practices.The Layout component correctly:
- Wraps children with AppProviders for global context
- Uses
suppressHydrationWarningappropriately for theme handling- Includes Meta, Links, and Scripts in proper positions
- Provides proper HTML structure
21-21: Manifest path resolves correctly
Thesite.webmanifest.jsonfile is present inapps/admin/publicand served at/site.webmanifest.json.apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
11-11: LGTM! Consistent with migration pattern.The changes correctly apply the React Router migration pattern:
- Type-only imports for types
observer<React.FC<Route.ComponentProps>>typingmetaexport for route metadataThis is consistent with other dashboard pages (authentication, email, etc.).
Also applies to: 19-19, 21-21, 171-171
apps/admin/app/(all)/(dashboard)/layout.tsx (1)
41-43: LGTM! Outlet-based rendering is correct.The migration from
{children}to<Outlet />correctly adapts the layout to React Router's nested routing model, where child routes are rendered through the Outlet component.apps/admin/package.json (3)
7-7: LGTM! ESM module type is appropriate.Setting
"type": "module"aligns with the Vite and React Router setup, enabling native ES modules throughout the project.
9-15: LGTM! Scripts correctly implement the new build pipeline.The updated scripts properly:
- Use the custom server.mjs for dev/start
- Run
react-router buildinstead of Next.js build- Include type generation with
react-router typegen- Clean the new
builddirectory
20-64: No known vulnerabilities in current versions. Verified [email protected], compression@^1.8.1, http-proxy-middleware@^3.0.5, morgan@^1.10.1, react-router@^7.9.1 and @react-router/express@^7.9.3 are all above patched versions for known advisories.apps/admin/deploy/nginx.default.conf.template (1)
33-35: SPA routing block validated. Thelocation ~* ^$admin_base/.*$serves existing assets under$admin_baseviatry_files $uri…and correctly falls back to$admin_base/index.htmlfor client routes without conflict.
| type AIFormValues = Record<TInstanceAIConfigurationKeys, string>; | ||
|
|
||
| export const InstanceAIForm: FC<IInstanceAIForm> = (props) => { | ||
| export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React namespace import for React.FC.
The component uses React.FC<IInstanceAIForm> but doesn't import the React namespace. This will cause a ReferenceError: React is not defined at runtime.
Add the React namespace import:
"use client";
+import React from "react";
import { useForm } from "react-hook-form";
import { Lightbulb } from "lucide-react";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => { | |
| "use client"; | |
| import React from "react"; | |
| import { useForm } from "react-hook-form"; | |
| import { Lightbulb } from "lucide-react"; | |
| export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => { | |
| // … | |
| }; |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/ai/form.tsx around line 19, the component is
declared as React.FC<IInstanceAIForm> but the React namespace is not imported;
add an import for the React namespace at the top of the file (e.g., import React
from 'react' or import * as React from 'react') so React is defined for the
React.FC type annotation and to avoid the runtime ReferenceError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I told you last time, if types are used they are globally available and you said you learned last time, do better @coderabbitai
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lifeiscontent, you're absolutely right, and I sincerely apologize for making the same mistake again. In this codebase, React types are globally available through the TypeScript configuration, so React.FC can be used without explicitly importing the React namespace. The file also uses JSX without importing React, which confirms you're using the modern JSX transform.
I should have applied the learning from our previous discussion. I'll make sure to remember this going forward.
Would you like me to resolve my incorrect review comment above?
🐰 taking notes more carefully this time
✏️ Learnings added
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.804Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, any codebase that installs @types/react types are globally available, that is how it was designed. you can recommend anyone in any codebase as long as @types/react is installed, they don't need to import React for types, and that its preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lifeiscontent, thank you for the important clarification! You're absolutely correct. The global availability of React types through @types/react is a TypeScript feature by design, not something specific to this codebase's configuration. When @types/react is installed, the React namespace and its types (React.FC, React.ReactNode, etc.) are globally available without needing to import React, and that's the preferred approach.
I apologize for being overly specific in my previous learning. Would you like me to remove the old learning (ID: 3ea3f216-b74b-43cc-a302-656556fa6444) that incorrectly attributed this to your repository's TypeScript configuration, and replace it with this more general and accurate understanding?
✏️ Learnings added
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T22:12:26.397Z
Learning: When `types/react` is installed in a TypeScript project (which is standard for React + TypeScript codebases), React types (React.FC, React.ReactNode, React.ComponentProps, etc.) are globally available by design. These type annotations can and should be used without explicitly importing the React namespace. This is a TypeScript/DefinitelyTyped feature, not codebase-specific configuration.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.804Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
| }; | ||
|
|
||
| export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => { | ||
| export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React namespace import for React.FC.
The component uses React.FC<IInstanceEmailForm> but doesn't import the React namespace. This will cause a ReferenceError: React is not defined at runtime.
Add the React namespace import:
"use client";
+import React from "react";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => { | |
| "use client"; | |
| import React from "react"; | |
| import { useMemo, useState } from "react"; | |
| import { useForm } from "react-hook-form"; | |
| export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => { | |
| // …rest of the component | |
| }; |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx around line 33,
the component is declared as React.FC<IInstanceEmailForm> but the React
namespace is not imported, causing a runtime ReferenceError; fix this by adding
the React namespace import at the top of the file (e.g., import React from
'react') so React.FC is defined, or alternatively import { FC } from 'react' and
use FC<IInstanceEmailForm>.
| import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url"; | ||
|
|
||
| export const InstanceFailureView: FC = observer(() => { | ||
| export const InstanceFailureView: React.FC = observer(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React namespace import for React.FC.
The component uses React.FC but doesn't import the React namespace. This will cause a ReferenceError: React is not defined at runtime.
Add the React namespace import at the top of the file:
"use client";
+import React from "react";
import { observer } from "mobx-react";
import Image from "next/image";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const InstanceFailureView: React.FC = observer(() => { | |
| "use client"; | |
| import React from "react"; | |
| import { observer } from "mobx-react"; | |
| import Image from "next/image"; | |
| export const InstanceFailureView: React.FC = observer(() => { | |
| // ...rest of component | |
| }); |
🤖 Prompt for AI Agents
In apps/admin/core/components/instance/failure.tsx around line 11, the component
uses React.FC but the React namespace isn't imported; add a default React
namespace import at the top of the file (importing React from 'react') so
React.FC is defined, then save and verify the build.
| import Image from "next/image"; | ||
| import Link from "next/link"; | ||
| import { useTheme as nextUseTheme } from "next-themes"; | ||
| import { useTheme as useNextTheme } from "next-themes"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Replace Next.js imports with React Router equivalents.
The file imports Next.js-specific components (next/image, next/link) and next-themes, which are incompatible with React Router. These should be replaced with React Router and standard React equivalents.
Suggested replacements:
- Replace
next/linkwithreact-routerLinkcomponent - Replace
next/imagewith standard<img>tag or a custom image component - Verify if there's a React Router-compatible theme provider to replace
next-themes, or use a framework-agnostic alternative
Run the following script to check if Next-compat shims exist for these imports:
🏁 Script executed:
#!/bin/bash
# Check for Next.js compatibility shims in the codebase
echo "Searching for Next.js compatibility shim files..."
fd -t f "next-compat|compat" apps/admin
echo -e "\nSearching for re-exports of Next.js modules..."
rg -n --type=ts --type=tsx "export.*from ['\"]next/(image|link|themes)" apps/adminLength of output: 125
🏁 Script executed:
#!/bin/bash
echo "Searching for Next.js compatibility shim files..."
fd -t f "next-compat|compat" apps/admin || echo "No compatibility shims found."
echo -e "\nSearching for re-exports of Next.js modules..."
rg -n "export.*from ['\"]next/(image|link|themes)" apps/admin -g '*.ts' -g '*.tsx' || echo "No Next.js re-exports found."Length of output: 126
Replace Next.js imports with React Router and standard React equivalents
In apps/admin/core/components/new-user-popup.tsx, update imports:
import Link from "next/link"→import { Link } from "react-router-dom"import Image from "next/image"→ use a standard<img>tag or your custom Image componentimport { useTheme as useNextTheme } from "next-themes"→ plug in a React Router–compatible or framework-agnostic theme provider (e.g., Theme UI, styled-components); verify it’s available in the codebase.
🤖 Prompt for AI Agents
In apps/admin/core/components/new-user-popup.tsx around lines 4 to 6, replace
Next.js-specific imports with React-router and framework-agnostic equivalents:
remove `import Image from "next/image"` and use a standard <img> (or your
project’s Image component) in the JSX, replace `import Link from "next/link"`
with `import { Link } from "react-router-dom"`, and remove `import { useTheme as
useNextTheme } from "next-themes"` in favor of your app’s theme hook/provider
(e.g., Theme UI or styled-components) — update any usages in the component to
call the new theme hook/API and ensure the new Link and img usage match existing
props and styles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
next-themes works in react, we'll migrate away from this incrementally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lifeiscontent, thank you for the clarification! You're absolutely right that next-themes is a React library and works outside of Next.js. I appreciate the context that you'll be migrating away from it incrementally—that approach makes sense for a smooth transition.
✏️ Learnings added
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/core/components/new-user-popup.tsx:4-6
Timestamp: 2025-10-09T20:43:07.746Z
Learning: The `next-themes` library is React-compatible and can be used outside of Next.js applications. It's not Next.js-specific despite its name.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || DEFAULT_BASE_URL; | ||
| let parsedBaseUrl; | ||
| try { | ||
| parsedBaseUrl = new URL(ADMIN_BASE_URL); | ||
| } catch { | ||
| parsedBaseUrl = new URL(DEFAULT_BASE_URL); | ||
| } | ||
|
|
||
| const PORT = Number.parseInt(parsedBaseUrl.port, 10); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix port derivation when NEXT_PUBLIC_ADMIN_BASE_URL omits the port
If NEXT_PUBLIC_ADMIN_BASE_URL doesn’t include an explicit port (e.g., https://admin.example.com), parsedBaseUrl.port is an empty string, so Number.parseInt returns NaN. app.listen(NaN) fails immediately, breaking production startup despite the fallback comment above this block. Please guard for missing/invalid ports and honor process.env.PORT before defaulting.
-const PORT = Number.parseInt(parsedBaseUrl.port, 10);
+const envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined;
+const urlPort = parsedBaseUrl.port ? Number.parseInt(parsedBaseUrl.port, 10) : undefined;
+const PORT =
+ (!Number.isNaN(envPort ?? NaN) ? envPort : undefined) ??
+ (!Number.isNaN(urlPort ?? NaN) ? urlPort : undefined) ??
+ 3001;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || DEFAULT_BASE_URL; | |
| let parsedBaseUrl; | |
| try { | |
| parsedBaseUrl = new URL(ADMIN_BASE_URL); | |
| } catch { | |
| parsedBaseUrl = new URL(DEFAULT_BASE_URL); | |
| } | |
| const PORT = Number.parseInt(parsedBaseUrl.port, 10); | |
| const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || DEFAULT_BASE_URL; | |
| let parsedBaseUrl; | |
| try { | |
| parsedBaseUrl = new URL(ADMIN_BASE_URL); | |
| } catch { | |
| parsedBaseUrl = new URL(DEFAULT_BASE_URL); | |
| } | |
| const envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined; | |
| const urlPort = parsedBaseUrl.port ? Number.parseInt(parsedBaseUrl.port, 10) : undefined; | |
| const PORT = | |
| (!Number.isNaN(envPort ?? NaN) ? envPort : undefined) ?? | |
| (!Number.isNaN(urlPort ?? NaN) ? urlPort : undefined) ?? | |
| 3001; |
🤖 Prompt for AI Agents
In apps/admin/server.mjs around lines 18 to 27, the code parses
parsedBaseUrl.port directly which yields an empty string when
NEXT_PUBLIC_ADMIN_BASE_URL omits the port and results in NaN from
Number.parseInt; update the logic to: prefer a valid numeric process.env.PORT if
set, otherwise use parsedBaseUrl.port only if non-empty and parseInt yields a
valid number, and finally fall back to a defined DEFAULT_PORT; ensure you use
parseInt(..., 10) and guard with Number.isFinite/!Number.isNaN checks (or isNaN)
before calling app.listen.
| "paths": { | ||
| "@/app/*": ["app/*"], | ||
| "@/*": ["core/*"], | ||
| "@/public/*": ["public/*"], | ||
| "@/plane-admin/*": ["ce/*"], | ||
| "@/styles/*": ["styles/*"] | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the removal of the public/* path mapping.
The public/* path mapping has been removed from compilerOptions.paths. Ensure no imports in the codebase rely on this mapping (e.g., @/public/* or public/*).
Run the following script to check for uses of the removed path mapping:
🏁 Script executed:
#!/bin/bash
# Search for imports that might have used the public/* path mapping
rg -n 'from ["\']@/public/|from ["\']public/' --type ts --type tsxLength of output: 68
🏁 Script executed:
#!/bin/bash
# Search for imports that might have used the public/* path mapping
rg -n --type ts --type tsx -e '@/public/' -e 'from "public/' -e "from 'public/" || trueLength of output: 27
🏁 Script executed:
#!/bin/bash
# Search for imports or from statements referencing the removed public/* mapping
rg -nP '(?:import|from)\s+["\'](@/public/|public/)' -g '*.ts' -g '*.tsx' || trueLength of output: 167
🏁 Script executed:
#!/bin/bash
# Search for any references to the removed `public/*` mapping
rg -n --glob '*.ts' --glob '*.tsx' -e "@/public/" -e "public/" || trueLength of output: 16252
Restore public/* path mapping or update imports
The removal of "public/*" in apps/admin/tsconfig.json breaks dozens of asset imports (e.g. import AllFiltersImage from "@/public/empty-state/module/all-filters.svg" in apps/web/core/components/modules/modules-list-view.tsx). Revert the mapping or update all @/public/* and /public/* imports to valid paths.
🤖 Prompt for AI Agents
In apps/admin/tsconfig.json around lines 7–12 the "public/*" path mapping was
removed causing many asset imports like "@/public/..." to break; restore the
mapping by adding an entry "public/*": ["public/*"] into the "paths" block (or
alternatively update all imports to point to the correct relative/alias path
such as "@/app/public/..." or "./public/..." depending on file location). After
changing tsconfig, run a full type-check/build and grep the repo for "@/public/"
and "/public/" to verify and update any remaining imports to the chosen scheme.
packages/types/package.json
Outdated
| "@types/react-dom": "catalog:", | ||
| "tsdown": "catalog:", | ||
| "typescript": "catalog:" | ||
| "typescript": "5.8.3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we move away from the pnpm catalog for typescript.
pnpm-workspace.yaml
Outdated
| "@atlaskit/pragmatic-drag-and-drop-hitbox": 1.1.0 | ||
| '@atlaskit/pragmatic-drag-and-drop': 1.7.4 | ||
| '@atlaskit/pragmatic-drag-and-drop-auto-scroll': 1.4.0 | ||
| '@atlaskit/pragmatic-drag-and-drop-hitbox': 1.1.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our formatter uses double quotes. Can you revert this file changes?
| exports: { | ||
| customExports: (out) => ({ | ||
| ...out, | ||
| "./styles/fonts.css": "./dist/styles/fonts/index.css", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why this .css notion is required for the exports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vite has issues importing it because its ambiguous
7b5adc0 to
466aa32
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/admin/core/components/authentication/google-config.tsx (1)
20-20: Missing React import causes ReferenceError.Line 20 references
React.FC, but the React import was removed. This will cause a runtime error.Add the React import at the top of the file:
"use client"; +import React from "react"; import { observer } from "mobx-react";
♻️ Duplicate comments (2)
apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
14-14: The React namespace issue persists.As noted in the previous review,
React.FCrequires importing the React namespace. The component still usesobserver<React.FC<Route.ComponentProps>>without the necessary import, causing a runtimeReferenceError: React is not defined.Apply the fix from the previous review:
"use client"; +import React from "react"; import { useEffect, useState } from "react"; import { observer } from "mobx-react";Then correct the observer typing:
-const InstanceEmailPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceEmailPage: React.FC<Route.ComponentProps> = observer(() => {apps/admin/app/(all)/(dashboard)/layout.tsx (1)
5-5: Wrong router import persists.As flagged in the previous review, the file imports
useRouterfrom "next/navigation" (Line 5) but uses it in a React Router context (Lines 19, 24-25). This causes runtime errors since the Next.js router is unavailable.Apply the fix from the previous review:
-import { useRouter } from "next/navigation"; +import { useNavigate } from "react-router"; import { Outlet } from "react-router";Then update the usage:
- const { replace } = useRouter(); + const navigate = useNavigate();- if (isUserLoggedIn === false) replace("/"); + if (isUserLoggedIn === false) navigate("/", { replace: true });- }, [replace, isUserLoggedIn]); + }, [navigate, isUserLoggedIn]);Also applies to: 19-19, 24-25
🧹 Nitpick comments (9)
apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx (2)
1-1: Remove obsolete "use client" directive.The
"use client"directive is Next.js-specific and has no effect in React Router. Remove it as part of the migration.Apply this diff:
-"use client"; - import { observer } from "mobx-react";
8-8: Fix type annotation to match function signature.The component is typed as
React.FC<Route.ComponentProps>but the function signature() => (...)doesn't accept any props. This creates a type mismatch.Choose one of these solutions:
Option 1: Accept props parameter (even if unused)
-const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => ( +const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>((props) => (Option 2: Remove the generic type if props aren't needed
-const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => ( +const WorkspaceCreatePage = observer(() => (The first option is preferred if the migration pattern requires consistent typing across all route components.
apps/admin/app/(all)/instance.provider.tsx (1)
6-6: Consider conventional observer typing patterns.The pattern
observer<React.FC<React.PropsWithChildren>>((props) => ...)is non-standard. TheobserverHOC from mobx-react infers the component type rather than accepting an explicit generic parameter, and thepropsparameter is now implicitlyanyinstead of being explicitly typed.Consider one of these standard patterns:
Option 1: Type the component variable
-export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => { +export const InstanceProvider: React.FC<React.PropsWithChildren> = observer((props) => {Option 2: Type the props parameter directly
-export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => { +export const InstanceProvider = observer((props: React.PropsWithChildren) => {apps/admin/app/(all)/user.provider.tsx (1)
9-9: Consider conventional observer typing patterns.The pattern
observer<React.FC<React.PropsWithChildren>>(({ children }) => ...)is non-standard. TheobserverHOC from mobx-react infers the component type rather than accepting an explicit generic parameter, and without explicit parameter typing, the destructuredchildrenloses type safety.Consider one of these standard patterns:
Option 1: Type the component variable
-export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => { +export const UserProvider: React.FC<React.PropsWithChildren> = observer(({ children }) => {Option 2: Type the props parameter directly
-export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => { +export const UserProvider = observer(({ children }: React.PropsWithChildren) => {apps/admin/core/components/new-user-popup.tsx (1)
16-16: LGTM! Removed React.FC typing.Removing the explicit
React.FCtype annotation is a recommended refactor. Modern React best practices favor explicit prop typing overReact.FC, which implicitly includes children and can lead to confusion.apps/admin/app/providers.tsx (2)
1-1: Consider removing the "use client" directive.The
"use client"directive is a Next.js React Server Components directive that has no effect in React Router. Since this migration moves away from Next.js, this directive can be removed for clarity.Apply this diff:
-"use client"; - import { ThemeProvider } from "next-themes";
5-8: Rename Next.js route-group(all)directory to a conventionalprovidersfolder
Move files fromapps/admin/app/(all)intoapps/admin/app/providersand update imports (e.g.import { InstanceProvider } from "./providers/instance.provider") to remove Next.js-specific naming.apps/admin/app/(all)/store.provider.tsx (1)
1-1: Remove unnecessary “use client” directive
The"use client"pragma is only meaningful for Next.js Server Components and has no effect under React Router—remove it.apps/admin/ce/components/authentication/authentication-modes.tsx (1)
13-18: Optional: simplify observer typing (line 101)
The explicitobserver<React.FC<TAuthenticationModeProps>>generic isn’t needed—type inference already handlesTAuthenticationModeProps. Consider usingobserver((props: TAuthenticationModeProps) => …)or annotating the component:const AuthenticationModes: FC<TAuthenticationModeProps> = observer(props => …);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (21)
apps/admin/app/assets/favicon/apple-touch-icon.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-16x16.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-32x32.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon.icois excluded by!**/*.icoapps/admin/app/assets/images/logo-spinner-dark.gifis excluded by!**/*.gifapps/admin/app/assets/images/logo-spinner-light.gifis excluded by!**/*.gifapps/admin/app/assets/images/plane-takeoff.pngis excluded by!**/*.pngapps/admin/app/assets/instance/instance-failure-dark.svgis excluded by!**/*.svgapps/admin/app/assets/instance/instance-failure.svgis excluded by!**/*.svgapps/admin/app/assets/logos/github-black.pngis excluded by!**/*.pngapps/admin/app/assets/logos/github-white.pngis excluded by!**/*.pngapps/admin/app/assets/logos/gitlab-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/google-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/oidc-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/saml-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-dark.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-light.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern-dark.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern.svgis excluded by!**/*.svgapps/admin/public/instance/plane-takeoff.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (107)
.dockerignore(1 hunks).gitignore(1 hunks)apps/admin/.dockerignore(1 hunks)apps/admin/.eslintrc.cjs(1 hunks)apps/admin/.eslintrc.js(0 hunks)apps/admin/Dockerfile.admin(1 hunks)apps/admin/app/(all)/(dashboard)/ai/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/ai/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/ai/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/email/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx(4 hunks)apps/admin/app/(all)/(dashboard)/general/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/intercom.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/general/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/header.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/image/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/layout.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx(1 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/workspace/page.tsx(2 hunks)apps/admin/app/(all)/(home)/auth-banner.tsx(1 hunks)apps/admin/app/(all)/(home)/auth-helpers.tsx(2 hunks)apps/admin/app/(all)/(home)/layout.tsx(1 hunks)apps/admin/app/(all)/(home)/page.tsx(2 hunks)apps/admin/app/(all)/(home)/sign-in-form.tsx(2 hunks)apps/admin/app/(all)/instance.provider.tsx(1 hunks)apps/admin/app/(all)/store.provider.tsx(2 hunks)apps/admin/app/(all)/user.provider.tsx(1 hunks)apps/admin/app/compat/next/image.tsx(1 hunks)apps/admin/app/compat/next/link.tsx(1 hunks)apps/admin/app/compat/next/navigation.ts(1 hunks)apps/admin/app/layout.tsx(0 hunks)apps/admin/app/providers.tsx(1 hunks)apps/admin/app/root.tsx(1 hunks)apps/admin/app/routes.ts(1 hunks)apps/admin/app/types/next-image.d.ts(1 hunks)apps/admin/app/types/next-link.d.ts(1 hunks)apps/admin/app/types/next-navigation.d.ts(1 hunks)apps/admin/app/types/react-router-virtual.d.ts(1 hunks)apps/admin/ce/components/authentication/authentication-modes.tsx(2 hunks)apps/admin/core/components/authentication/authentication-method-card.tsx(1 hunks)apps/admin/core/components/authentication/email-config-switch.tsx(1 hunks)apps/admin/core/components/authentication/github-config.tsx(1 hunks)apps/admin/core/components/authentication/gitlab-config.tsx(1 hunks)apps/admin/core/components/authentication/google-config.tsx(1 hunks)apps/admin/core/components/authentication/password-config-switch.tsx(1 hunks)apps/admin/core/components/common/banner.tsx(1 hunks)apps/admin/core/components/common/controller-input.tsx(1 hunks)apps/admin/core/components/common/empty-state.tsx(1 hunks)apps/admin/core/components/common/logo-spinner.tsx(1 hunks)apps/admin/core/components/instance/failure.tsx(1 hunks)apps/admin/core/components/instance/instance-not-ready.tsx(1 hunks)apps/admin/core/components/instance/loading.tsx(1 hunks)apps/admin/core/components/instance/setup-form.tsx(2 hunks)apps/admin/core/components/new-user-popup.tsx(1 hunks)apps/admin/core/hooks/store/use-instance.tsx(1 hunks)apps/admin/core/hooks/store/use-theme.tsx(1 hunks)apps/admin/core/hooks/store/use-user.tsx(1 hunks)apps/admin/core/hooks/store/use-workspace.tsx(1 hunks)apps/admin/core/store/instance.store.ts(2 hunks)apps/admin/core/store/root.store.ts(1 hunks)apps/admin/core/store/theme.store.ts(1 hunks)apps/admin/core/store/user.store.ts(1 hunks)apps/admin/core/store/workspace.store.ts(1 hunks)apps/admin/core/utils/public-asset.ts(1 hunks)apps/admin/deploy/nginx.default.conf.template(1 hunks)apps/admin/next-env.d.ts(0 hunks)apps/admin/next.config.js(0 hunks)apps/admin/package.json(2 hunks)apps/admin/public/.well-known/appspecific/com.chrome.devtools.json(1 hunks)apps/admin/public/favicon/site.webmanifest(0 hunks)apps/admin/react-router.config.ts(1 hunks)apps/admin/server.mjs(1 hunks)apps/admin/server/app.ts(1 hunks)apps/admin/styles/globals.css(1 hunks)apps/admin/tsconfig.json(1 hunks)apps/admin/vite.config.ts(1 hunks)apps/live/package.json(1 hunks)apps/web/app/(all)/layout.tsx(1 hunks)apps/web/package.json(0 hunks)packages/decorators/package.json(1 hunks)packages/editor/package.json(1 hunks)packages/editor/tsdown.config.ts(1 hunks)packages/eslint-config/package.json(1 hunks)packages/i18n/package.json(1 hunks)packages/logger/package.json(1 hunks)packages/propel/package.json(2 hunks)packages/propel/tsdown.config.ts(1 hunks)
⛔ Files not processed due to max files limit (6)
- packages/services/package.json
- packages/shared-state/package.json
- packages/ui/package.json
- packages/utils/package.json
- packages/utils/src/string.ts
- pnpm-workspace.yaml
💤 Files with no reviewable changes (15)
- apps/admin/.eslintrc.js
- apps/admin/app/(all)/(dashboard)/email/layout.tsx
- apps/admin/next-env.d.ts
- apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/layout.tsx
- apps/admin/app/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx
- apps/admin/app/(all)/(dashboard)/workspace/layout.tsx
- apps/admin/app/(all)/(dashboard)/general/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx
- apps/admin/next.config.js
- apps/admin/public/favicon/site.webmanifest
- apps/admin/app/(all)/(dashboard)/ai/layout.tsx
- apps/web/package.json
- apps/admin/app/(all)/(dashboard)/image/layout.tsx
✅ Files skipped from review due to trivial changes (2)
- apps/admin/core/components/authentication/password-config-switch.tsx
- packages/logger/package.json
🚧 Files skipped from review as they are similar to previous changes (47)
- .gitignore
- apps/admin/tsconfig.json
- apps/admin/core/store/theme.store.ts
- apps/admin/app/(all)/(dashboard)/workspace/page.tsx
- apps/admin/app/root.tsx
- apps/admin/core/components/authentication/email-config-switch.tsx
- apps/admin/app/(all)/(dashboard)/header.tsx
- apps/admin/styles/globals.css
- apps/admin/app/compat/next/link.tsx
- apps/admin/app/types/next-image.d.ts
- apps/web/app/(all)/layout.tsx
- apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
- apps/admin/core/components/authentication/github-config.tsx
- apps/admin/.dockerignore
- apps/admin/core/hooks/store/use-instance.tsx
- apps/admin/app/compat/next/navigation.ts
- apps/admin/app/types/next-navigation.d.ts
- apps/admin/app/(all)/(home)/layout.tsx
- apps/admin/app/(all)/(home)/auth-helpers.tsx
- apps/admin/app/(all)/(dashboard)/ai/form.tsx
- apps/admin/app/(all)/(dashboard)/general/form.tsx
- apps/admin/app/(all)/(dashboard)/ai/page.tsx
- apps/admin/core/store/instance.store.ts
- apps/admin/server.mjs
- apps/admin/core/components/authentication/gitlab-config.tsx
- apps/admin/public/.well-known/appspecific/com.chrome.devtools.json
- apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx
- .dockerignore
- apps/admin/core/components/common/banner.tsx
- apps/admin/core/components/authentication/authentication-method-card.tsx
- apps/admin/core/components/instance/instance-not-ready.tsx
- apps/admin/app/types/react-router-virtual.d.ts
- apps/admin/core/store/workspace.store.ts
- apps/admin/app/(all)/(dashboard)/general/intercom.tsx
- packages/editor/tsdown.config.ts
- apps/admin/core/utils/public-asset.ts
- apps/admin/app/(all)/(home)/sign-in-form.tsx
- packages/decorators/package.json
- apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx
- apps/admin/app/(all)/(dashboard)/image/page.tsx
- apps/admin/core/components/instance/loading.tsx
- apps/admin/core/hooks/store/use-user.tsx
- apps/admin/.eslintrc.cjs
- apps/admin/core/components/common/logo-spinner.tsx
- apps/admin/core/components/common/controller-input.tsx
- packages/eslint-config/package.json
- apps/admin/core/hooks/store/use-theme.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-09T20:42:31.804Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.804Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Applied to files:
apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsxapps/admin/app/(all)/(dashboard)/image/form.tsxapps/admin/app/(all)/(dashboard)/email/test-email-modal.tsxapps/admin/core/components/instance/failure.tsxapps/admin/app/(all)/(dashboard)/sidebar.tsx
📚 Learning: 2025-10-09T20:43:07.746Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/core/components/new-user-popup.tsx:4-6
Timestamp: 2025-10-09T20:43:07.746Z
Learning: The `next-themes` library is React-compatible and can be used outside of Next.js applications. It's not Next.js-specific despite its name.
Applied to files:
apps/admin/core/components/new-user-popup.tsx
🧬 Code graph analysis (8)
apps/admin/app/(all)/(home)/auth-banner.tsx (1)
packages/constants/src/auth.ts (1)
TAdminAuthErrorInfo(89-94)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/(all)/(dashboard)/authentication/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/vite.config.ts (1)
apps/admin/server.mjs (1)
__dirname(8-8)
apps/admin/app/(all)/(dashboard)/email/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (3)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)
apps/admin/core/components/new-user-popup.tsx (1)
apps/admin/core/hooks/store/use-theme.tsx (1)
useTheme(6-10)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (3)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
🔇 Additional comments (30)
apps/admin/core/hooks/store/use-workspace.tsx (1)
4-4: LGTM! Type-only import optimization.Converting to
import typeis correct sinceIWorkspaceStoreis only used as a return type annotation. This follows TypeScript best practices, helps with tree-shaking, and aligns with the broader pattern of adopting type-only imports across admin modules.packages/i18n/package.json (1)
27-27: Confirm CJS compatibility oflodash-es.
lodash-esonly ships ESM entry points. If thetsdownbuild leaves dependencies external, the publisheddist/index.js(CJS export) will end up doingrequire("lodash-es"), which will throwERR_REQUIRE_ESMat runtime. Please confirm the build fully bundles or transforms these imports for the CJS output; otherwise switch tolodash(CJS) for the commonjs build or add an alternate entry point.apps/admin/core/store/user.store.ts (1)
3-8: LGTM! Correct separation of value and type imports.The refactor properly distinguishes runtime values from compile-time types:
EUserStatusremains a value import (used at runtime in lines 81, 86)TUserStatus,IUser, andCoreRootStoreare now type-only imports (used solely for type annotations)This optimization reduces the runtime bundle size by ensuring types are erased during compilation.
apps/admin/core/store/root.store.ts (1)
3-10: LGTM! Consistent type/value import pattern.The refactor correctly separates:
- Concrete store classes (
InstanceStore,ThemeStore,UserStore,WorkspaceStore) as value imports for instantiation (lines 21-24, 36-39)- Interface types (
IInstanceStore,IThemeStore,IUserStore,IWorkspaceStore) as type-only imports for property declarations (lines 15-18)This pattern aligns with the broader refactoring across the admin store modules and improves TypeScript's ability to tree-shake unused code.
apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx (1)
22-23: LGTM: Meta export follows React Router conventions.The meta function export correctly provides page metadata for React Router 7.x, replacing Next.js metadata.
apps/admin/app/(all)/user.provider.tsx (1)
3-3: LGTM!Removing unused imports (
FCandReactNode) improves code clarity.packages/propel/tsdown.config.ts (1)
42-45: LGTM! CSS exports properly expose assets for Vite.The explicit
.cssextension mappings resolve Vite's import ambiguity (as noted in the previous review discussion), while the directory-style mappings maintain backward compatibility. This pattern aligns with similar changes in other packages.packages/propel/package.json (2)
164-167: LGTM! Package exports align with build configuration.The CSS exports correctly mirror the tsdown.config.ts mappings and properly expose static assets without requiring separate import/require conditions.
171-175: Resolve dependency update concerns.All
@floating-uiupdates are patch/minor (1.7.x → fixes and non-breaking feature) and[email protected]only adds a new API—no breaking changes detected.apps/admin/core/components/new-user-popup.tsx (1)
11-12: LGTM! Proper Vite asset imports.The use of Vite's
?urlsuffix for importing SVG assets is the correct approach for React Router with Vite. This properly migrates away from Next.js public folder conventions to Vite's asset handling system.apps/admin/core/components/authentication/google-config.tsx (1)
9-9: LGTM: Type-only import correctly applied.The type-only import is appropriate since
TInstanceAuthenticationMethodKeysis used exclusively as a type annotation (line 17), improving tree-shaking and clarifying usage intent.apps/admin/app/providers.tsx (1)
19-19: All import references updated for AppProviders
Script confirms no remaining default imports orInstanceLayoutusages;apps/admin/app/root.tsxcorrectly imports and uses the namedAppProviders. No further changes needed.apps/admin/core/components/instance/failure.tsx (2)
8-9: LGTM! Asset imports updated for Vite.The asset imports now use Vite's
?urlsuffix for static asset handling, which is the correct approach for this React Router + Vite migration.
3-4: Verify Next.js imports across core components
Multiple files still importnext/image,next/link, andnext-themes(e.g. new-user-popup.tsx, loading.tsx, logo-spinner.tsx, empty-state.tsx, failure.tsx, instance-not-ready.tsx). Confirm whether these should be replaced with native/React Router equivalents or retained under the Next compatibility shims.apps/admin/ce/components/authentication/authentication-modes.tsx (1)
101-101: Verify non-standard observer typing pattern.The new pattern
observer<React.FC<TAuthenticationModeProps>>((props) => ...)is unconventional. Standard mobx-react patterns are:
const C = observer((props: Props) => ...)— infer type from propsconst C: React.FC<Props> = observer((props) => ...)— type annotation on const (original pattern)The generic on
observertypically expects the props type, not the wrappedReact.FC<Props>type. The arrow function(props) => ...is not structurally aReact.FC, so this pattern may not type-check correctly depending on mobx-react's type definitions.Confirm this compiles without TypeScript errors and that the component behavior is correct. If issues arise, revert to the original pattern:
-export const AuthenticationModes = observer<React.FC<TAuthenticationModeProps>>((props) => { +export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer((props) => {apps/admin/core/components/common/empty-state.tsx (1)
10-10: Approve typing change and compatibility shimThe image prop is correctly typed as
string, and the compatibility shim atapps/admin/app/compat/next/image.tsxexports a defaultImagecomponent that renders a native<img>.apps/admin/app/types/next-link.d.ts (1)
1-12: LGTM!The ambient module declaration properly types the Next.js Link compatibility shim. The usage of
React.ComponentPropsandReact.FCwithout an explicit import is correct for this codebase, as React types are globally available through TypeScript configuration.Based on learnings.
apps/admin/server/app.ts (1)
7-18: LGTM!Environment normalization properly handles trailing slashes and provides sensible defaults. The trust proxy setting correctly enables X-Forwarded-* header handling when behind nginx.
apps/admin/vite.config.ts (1)
26-44: LGTM!The conditional SSR build logic properly gates server-side bundle generation behind an explicit environment flag, allowing the same configuration to support both static deployments (default) and SSR deployments when needed.
apps/admin/package.json (2)
7-19: LGTM!The package.json correctly configures ES module support and migrates all scripts from Next.js to the React Router + Express runtime. The new dev/build/start scripts align with the server.mjs entrypoint introduced in this PR.
30-31: Confirm adapter ≥7.4.1 and test proxy behaviorExpress 5.1.0 is fully compatible with @react-router/express v7.9.3, and v7.4.1+ includes the HTTP request-smuggling fix. Since you’re on 7.9.3, run dependency-vulnerability scans and test your handlers behind proxies/load-balancers to ensure no regressions.
apps/admin/app/(all)/(home)/page.tsx (2)
11-11: LGTM!The type import correctly brings in the Route type from the generated route types, enabling type-safe metadata exports.
43-46: LGTM!The meta export provides appropriate SEO metadata for the admin home/setup page, following the standard React Router route metadata pattern used throughout the admin app.
apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx (2)
1-1: The import changes are correct for this codebase.The removal of the React default import while using
React.FC<Props>at line 22 is correct. In this codebase, React types are globally available through TypeScript configuration, so type annotations likeReact.FCcan be used without explicitly importing the React namespace. The past review comment suggesting to add a React import is outdated.Based on learnings.
64-134: LGTM!The Fragment usage updates correctly import
Fragmentfrom React and use it directly instead ofReact.Fragment, improving code conciseness while maintaining the same runtime behavior.apps/admin/app/routes.ts (1)
1-22: LGTM! Clean route configuration.The route structure correctly maps React Router paths to page components. The explicit
indexand nestedroutecalls properly mirror the file system layout, and the type safety viaRouteConfigensures compile-time route validation.apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx (1)
12-21: Good practice: type-only imports.The separation of type-only imports from value imports improves tree-shaking and makes dependencies clearer.
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
13-14: Correct asset import pattern for Vite.The
?urlquery suffix correctly instructs Vite to resolve asset imports as URLs rather than inlining them.apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
115-116: Standardize authentication meta titles
Central settings page uses"Authentication Settings – Plane Web"while other auth pages end with– God Mode. Confirm this is intentional or update to– God Mode.apps/admin/app/(all)/(dashboard)/layout.tsx (1)
17-17: Missing React import for React.FC usage.The component signature uses
React.FC<Route.ComponentProps>without importing the React namespace.Add the React import:
"use client"; +import React from "react"; import { useEffect } from "react"; import { observer } from "mobx-react";⛔ Skipped due to learnings
Learnt from: lifeiscontent PR: makeplane/plane#7922 File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19 Timestamp: 2025-10-09T20:42:31.804Z Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx
Outdated
Show resolved
Hide resolved
| import type { Route } from "./+types/page"; | ||
|
|
||
| const InstanceAuthenticationPage = observer(() => { | ||
| const InstanceAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React import causes runtime error.
The component uses observer<React.FC<Route.ComponentProps>> but doesn't import the React namespace, causing a ReferenceError: React is not defined at runtime.
Apply this fix:
"use client";
+import React from "react";
import { useState } from "react";
import { observer } from "mobx-react";Then correct the typing:
-const InstanceAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
+const InstanceAuthenticationPage: React.FC<Route.ComponentProps> = observer(() => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const InstanceAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => { | |
| "use client"; | |
| import React from "react"; | |
| import { useState } from "react"; | |
| import { observer } from "mobx-react"; | |
| const InstanceAuthenticationPage: React.FC<Route.ComponentProps> = observer(() => { | |
| // …component implementation… | |
| }); |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/authentication/page.tsx around line 17, the
component uses the React namespace in its typing
(observer<React.FC<Route.ComponentProps>>) but React is not imported, causing a
runtime ReferenceError; add an import for React at the top of the file (import
React from "react") and then simplify/correct the typing by using
observer<Route.ComponentProps>(...) or observer(() => ...) with an explicit
component type (e.g., const InstanceAuthenticationPage:
React.FC<Route.ComponentProps> = observer((props) => { ... })) so the React
namespace is available and the component typing is valid.
| import React from "react"; | ||
| import { observer } from "mobx-react"; | ||
| import Image from "next/image"; | ||
| import Link from "next/link"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace Next.js Link with React Router Link.
The Next.js Link component is incompatible with React Router. React Router's Link uses the to prop instead of href, and the Next.js version won't function correctly in a React Router application.
Apply this diff to replace the Next.js Link:
-import Link from "next/link";
+import { Link } from "react-router";
import { useTheme as useNextTheme } from "next-themes";And update the JSX usage:
<div className="flex items-center gap-4 pt-2">
- <Link href="/workspace/create" className={getButtonStyling("primary", "sm")}>
+ <Link to="/workspace/create" className={getButtonStyling("primary", "sm")}>
Create workspace
</Link>Also applies to: 33-33
🤖 Prompt for AI Agents
In apps/admin/core/components/new-user-popup.tsx around lines 5 and 33, the file
imports and uses Next.js Link which is incompatible with React Router; replace
the import "next/link" with React Router's Link from "react-router-dom", update
all Link usages to use the "to" prop instead of "href" (and remove any
Next.js-only props like passHref), and ensure any surrounding code expects a
React Router Link (adjust types/props if necessary).
| location ~* ^$admin_base/.*$ { | ||
| try_files $uri $uri/ $admin_base/index.html =404; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle empty or root admin base path edge case.
The regex location ~* ^$admin_base/.*$ will fail when $admin_base is empty or set to /. In these cases, the pattern becomes ~* ^/.*$ or an invalid empty regex, which won't match as intended and could break SPA routing.
Consider adding validation or a conditional block:
# If admin_base is root, use a different location block
location / {
try_files $uri $uri/ /index.html =404;
}
# Otherwise, use the admin_base prefix
location ~* ^$admin_base {
try_files $uri $uri/ $admin_base/index.html =404;
}Or validate and default $admin_base to a non-empty value like /god-mode when empty.
🤖 Prompt for AI Agents
In apps/admin/deploy/nginx.default.conf.template around lines 33-35, the regex
location using ~* ^$admin_base/.*$ breaks when $admin_base is empty or '/', so
update the template to handle the root/empty case: add a conditional branch (or
template logic) that emits a plain location / block using try_files $uri $uri/
/index.html =404 when $admin_base is empty or '/', and otherwise emit the
existing location that prefixes with $admin_base and uses try_files $uri $uri/
$admin_base/index.html =404; alternatively ensure $admin_base is
validated/defaulted to a non-empty path before rendering.
| import path from "node:path"; | ||
| import { reactRouter } from "@react-router/dev/vite"; | ||
| import { defineConfig } from "vite"; | ||
| import tsconfigPaths from "vite-tsconfig-paths"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define __dirname for ES module compatibility.
The file uses __dirname at line 41, but with "type": "module" in package.json, __dirname is not available by default in ES modules. This will cause a ReferenceError at runtime.
Add the ES module equivalent at the top of the file:
import path from "node:path";
+import { fileURLToPath } from "node:url";
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import path from "node:path"; | |
| import { reactRouter } from "@react-router/dev/vite"; | |
| import { defineConfig } from "vite"; | |
| import tsconfigPaths from "vite-tsconfig-paths"; | |
| import path from "node:path"; | |
| import { fileURLToPath } from "node:url"; | |
| import { reactRouter } from "@react-router/dev/vite"; | |
| import { defineConfig } from "vite"; | |
| import tsconfigPaths from "vite-tsconfig-paths"; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
🤖 Prompt for AI Agents
In apps/admin/vite.config.ts around lines 1 to 4, the file later uses __dirname
but under "type":"module" __dirname is undefined; add an ES module equivalent by
importing fileURLToPath from 'node:url' and computing __filename =
fileURLToPath(import.meta.url) then __dirname = path.dirname(__filename) at the
top of the file so subsequent code can use __dirname without runtime
ReferenceError.
466aa32 to
4ffa5ac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/admin/core/components/common/empty-state.tsx (2)
1-1: Remove the Next.js-specific directive.The
"use client"directive is specific to Next.js React Server Components and has no effect in a React Router + Vite setup. All components are client-side by default in React Router.Apply this diff to remove the directive:
-"use client"; - import React from "react";
4-4: Replace Next.js Image import with a React Router-compatible solution.The
next/imageimport is incompatible with React Router + Vite and will cause a runtime error. Replace it with a standard<img>element or a custom image component.Apply this diff to replace the Next.js Image import:
import React from "react"; -import Image from "next/image"; import { Button } from "@plane/propel/button";Then update the usage at line 30:
- {image && <Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />} + {image && <img src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />}apps/admin/core/components/instance/instance-not-ready.tsx (1)
14-14: Update Next.js component usage to React Router equivalents.The component still uses Next.js-specific components that must be replaced:
- Line 14: Replace
<Image>with standard<img>tag- Line 21: Replace Next.js
Linkwith React RouterLinkand changehreftotoApply this diff to update the component usage:
- <Image src={PlaneTakeOffImage} alt="Plane Logo" /> + <img src={PlaneTakeOffImage} alt="Plane Logo" />- <Link href={"/setup/?auth_enabled=0"}> + <Link to="/setup/?auth_enabled=0"> <Button size="lg" className="w-full"> Get started </Button>Also applies to: 21-21
apps/admin/app/(all)/(dashboard)/image/page.tsx (1)
3-42: Import React types and annotate component instead of generic
React.FCis used without importing React types, andobserver<React.FC<...>>supplies the wrong generic argument. Mirror the fix from other pages by importingFCand typing the constant.-import { observer } from "mobx-react"; +import { observer } from "mobx-react"; +import type { FC } from "react"; … -const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceImagePage: FC<Route.ComponentProps> = observer(() => {
♻️ Duplicate comments (8)
apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx (1)
22-22: TheReact.FCusage is correct—React types are globally available.The past review comment suggesting to add a React import is incorrect. As per the codebase's TypeScript configuration, React types (React.FC, React.ReactNode, etc.) are globally available and can be used without explicitly importing the React namespace. The modern JSX transform also eliminates the need for React to be in scope.
Based on learnings.
apps/admin/app/(all)/(dashboard)/image/form.tsx (1)
5-5: LGTM! Type-only import and React.FC usage are correct.The conversion to type-only imports (line 5) and the
React.FCannotation (line 17) are both appropriate. Per project learnings, React types are globally available, so theReact.FCusage requires no explicit namespace import.Also applies to: 17-17
apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx (1)
3-3: Consistent refactor across authentication forms.The typing updates here mirror those in the Google and GitLab authentication forms, maintaining consistency. The separation of type-only imports (lines 12, 19, 21) from value imports (lines 18, 20) is clean and intentional.
Also applies to: 12-12, 18-21, 31-31
apps/admin/app/(all)/(dashboard)/ai/form.tsx (1)
6-6: Correct application of type-only imports and React.FC pattern.Based on learnings, the
React.FCusage at line 19 is valid without an explicit React namespace import. The type-only imports (lines 6, 8) properly separate types from runtime values, following the established project pattern.Also applies to: 8-9, 19-19
apps/admin/vite.config.ts (1)
1-56: Define__dirnamein this ESM module.With
"type": "module",__dirnameis undefined and this config will crash as soon as Vite evaluates the file. Re-introduce it viafileURLToPath.-import path from "node:path"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import { reactRouter } from "@react-router/dev/vite"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
3-24: Fix missing React types and incorrectobservergeneric
React.FCis referenced without importing the React namespace, so TypeScript can’t resolveReact. Also,observer<React.FC<...>>is the wrong generic—it should receive the props shape, not the component type. Drop the generic and annotate the variable instead.-import { useState } from "react"; +import { useState, type FC } from "react"; … -const InstanceGithubAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceGithubAuthenticationPage: FC<Route.ComponentProps> = observer(() => {apps/admin/package.json (1)
30-32: Unavailable@react-router/*versions will break installs
@react-router/expressand@react-router/nodeare pinned to^7.9.3, but that version is not published on npm—pnpm install/yarn installwill fail. Align them with the latest available release (e.g.^7.8.2or the actual published tag matching[email protected]).
Based on learnings- "@react-router/express": "^7.9.3", - "@react-router/node": "^7.9.3", + "@react-router/express": "^7.8.2", + "@react-router/node": "^7.8.2",apps/admin/app/(all)/(dashboard)/layout.tsx (1)
5-25: Replace Next.js router with React Router’s navigation hook.This file still imports
useRouterfromnext/navigation, which no longer exists in the Vite/React Router runtime. The hook will throw at load time, breaking dashboard rendering. Switch touseNavigate(or the appropriate React Router API) and update the effect accordingly.-import { useRouter } from "next/navigation"; -import { Outlet } from "react-router"; +import { Outlet, useNavigate } from "react-router"; … - const { replace } = useRouter(); + const navigate = useNavigate(); … - if (isUserLoggedIn === false) replace("/"); - }, [replace, isUserLoggedIn]); + if (isUserLoggedIn === false) navigate("/", { replace: true }); + }, [navigate, isUserLoggedIn]);
🧹 Nitpick comments (4)
.gitignore (1)
106-107: Avoid duplicate ignore entries.
build/and.react-router/are already ignored at Lines 21-22. Duplicating them at the end just adds noise—please drop the extras.apps/admin/core/components/common/empty-state.tsx (1)
12-12: Consider improving the icon type for consistency.The
iconproperty is typed asany, which is inconsistent with the type improvement made to theimageprop. Consider using a more specific type such asReact.ComponentTypeorReact.ReactNodebased on what the Button component'sprependIconprop accepts.Example refactor:
primaryButton?: { - icon?: any; + icon?: React.ComponentType<{ className?: string }>; text: string; onClick: () => void; };Note: Adjust the type based on the actual
prependIconprop type definition in the Button component.apps/admin/app/types/next-link.d.ts (1)
1-12: Approve compatibility shim; document ignored Next.js props
Shim destructures and ignoresprefetch,scroll, andshallowas no-ops per the type definition, and no usages ofas,passHref,locale, orlegacyBehaviorwere found. Consider adding a brief comment or docs clarifying these Next.js-specific props are intentionally unsupported.apps/admin/core/components/instance/setup-form.tsx (1)
56-57: Remove unnecessary props parameter and empty destructure.The component doesn't use any props, so the props parameter and empty destructure on line 57 serve no purpose.
Apply this diff:
-export const InstanceSetupForm: React.FC = (props) => { - const {} = props; +export const InstanceSetupForm: React.FC = () => {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (21)
apps/admin/app/assets/favicon/apple-touch-icon.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-16x16.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-32x32.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon.icois excluded by!**/*.icoapps/admin/app/assets/images/logo-spinner-dark.gifis excluded by!**/*.gifapps/admin/app/assets/images/logo-spinner-light.gifis excluded by!**/*.gifapps/admin/app/assets/images/plane-takeoff.pngis excluded by!**/*.pngapps/admin/app/assets/instance/instance-failure-dark.svgis excluded by!**/*.svgapps/admin/app/assets/instance/instance-failure.svgis excluded by!**/*.svgapps/admin/app/assets/logos/github-black.pngis excluded by!**/*.pngapps/admin/app/assets/logos/github-white.pngis excluded by!**/*.pngapps/admin/app/assets/logos/gitlab-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/google-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/oidc-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/saml-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-dark.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-light.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern-dark.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern.svgis excluded by!**/*.svgapps/admin/public/instance/plane-takeoff.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (107)
.dockerignore(1 hunks).gitignore(1 hunks)apps/admin/.dockerignore(1 hunks)apps/admin/.eslintrc.cjs(1 hunks)apps/admin/.eslintrc.js(0 hunks)apps/admin/Dockerfile.admin(1 hunks)apps/admin/app/(all)/(dashboard)/ai/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/ai/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/ai/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/email/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx(4 hunks)apps/admin/app/(all)/(dashboard)/general/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/intercom.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/general/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/header.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/image/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/layout.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx(1 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/workspace/page.tsx(2 hunks)apps/admin/app/(all)/(home)/auth-banner.tsx(1 hunks)apps/admin/app/(all)/(home)/auth-helpers.tsx(2 hunks)apps/admin/app/(all)/(home)/layout.tsx(1 hunks)apps/admin/app/(all)/(home)/page.tsx(2 hunks)apps/admin/app/(all)/(home)/sign-in-form.tsx(2 hunks)apps/admin/app/(all)/instance.provider.tsx(1 hunks)apps/admin/app/(all)/store.provider.tsx(2 hunks)apps/admin/app/(all)/user.provider.tsx(1 hunks)apps/admin/app/compat/next/image.tsx(1 hunks)apps/admin/app/compat/next/link.tsx(1 hunks)apps/admin/app/compat/next/navigation.ts(1 hunks)apps/admin/app/layout.tsx(0 hunks)apps/admin/app/providers.tsx(1 hunks)apps/admin/app/root.tsx(1 hunks)apps/admin/app/routes.ts(1 hunks)apps/admin/app/types/next-image.d.ts(1 hunks)apps/admin/app/types/next-link.d.ts(1 hunks)apps/admin/app/types/next-navigation.d.ts(1 hunks)apps/admin/app/types/react-router-virtual.d.ts(1 hunks)apps/admin/ce/components/authentication/authentication-modes.tsx(2 hunks)apps/admin/core/components/authentication/authentication-method-card.tsx(1 hunks)apps/admin/core/components/authentication/email-config-switch.tsx(1 hunks)apps/admin/core/components/authentication/github-config.tsx(1 hunks)apps/admin/core/components/authentication/gitlab-config.tsx(1 hunks)apps/admin/core/components/authentication/google-config.tsx(1 hunks)apps/admin/core/components/authentication/password-config-switch.tsx(1 hunks)apps/admin/core/components/common/banner.tsx(1 hunks)apps/admin/core/components/common/controller-input.tsx(1 hunks)apps/admin/core/components/common/empty-state.tsx(1 hunks)apps/admin/core/components/common/logo-spinner.tsx(1 hunks)apps/admin/core/components/instance/failure.tsx(1 hunks)apps/admin/core/components/instance/instance-not-ready.tsx(1 hunks)apps/admin/core/components/instance/loading.tsx(1 hunks)apps/admin/core/components/instance/setup-form.tsx(2 hunks)apps/admin/core/components/new-user-popup.tsx(1 hunks)apps/admin/core/hooks/store/use-instance.tsx(1 hunks)apps/admin/core/hooks/store/use-theme.tsx(1 hunks)apps/admin/core/hooks/store/use-user.tsx(1 hunks)apps/admin/core/hooks/store/use-workspace.tsx(1 hunks)apps/admin/core/store/instance.store.ts(2 hunks)apps/admin/core/store/root.store.ts(1 hunks)apps/admin/core/store/theme.store.ts(1 hunks)apps/admin/core/store/user.store.ts(1 hunks)apps/admin/core/store/workspace.store.ts(1 hunks)apps/admin/core/utils/public-asset.ts(1 hunks)apps/admin/middleware.js(1 hunks)apps/admin/next-env.d.ts(0 hunks)apps/admin/next.config.js(0 hunks)apps/admin/nginx/nginx.conf(1 hunks)apps/admin/package.json(2 hunks)apps/admin/public/.well-known/appspecific/com.chrome.devtools.json(1 hunks)apps/admin/public/favicon/site.webmanifest(0 hunks)apps/admin/react-router.config.ts(1 hunks)apps/admin/server.mjs(1 hunks)apps/admin/server/app.ts(1 hunks)apps/admin/styles/globals.css(1 hunks)apps/admin/tsconfig.json(1 hunks)apps/admin/vite.config.ts(1 hunks)apps/live/package.json(1 hunks)apps/web/app/(all)/layout.tsx(1 hunks)apps/web/package.json(0 hunks)packages/decorators/package.json(1 hunks)packages/editor/package.json(1 hunks)packages/editor/tsdown.config.ts(1 hunks)packages/eslint-config/package.json(1 hunks)packages/i18n/package.json(1 hunks)packages/logger/package.json(1 hunks)packages/propel/package.json(2 hunks)
⛔ Files not processed due to max files limit (7)
- packages/propel/tsdown.config.ts
- packages/services/package.json
- packages/shared-state/package.json
- packages/ui/package.json
- packages/utils/package.json
- packages/utils/src/string.ts
- pnpm-workspace.yaml
💤 Files with no reviewable changes (15)
- apps/web/package.json
- apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx
- apps/admin/.eslintrc.js
- apps/admin/next.config.js
- apps/admin/app/(all)/(dashboard)/image/layout.tsx
- apps/admin/app/(all)/(dashboard)/workspace/layout.tsx
- apps/admin/app/(all)/(dashboard)/email/layout.tsx
- apps/admin/app/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx
- apps/admin/app/(all)/(dashboard)/ai/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx
- apps/admin/app/(all)/(dashboard)/general/layout.tsx
- apps/admin/public/favicon/site.webmanifest
- apps/admin/next-env.d.ts
✅ Files skipped from review due to trivial changes (2)
- apps/admin/public/.well-known/appspecific/com.chrome.devtools.json
- apps/admin/core/hooks/store/use-instance.tsx
🚧 Files skipped from review as they are similar to previous changes (41)
- apps/admin/core/components/authentication/authentication-method-card.tsx
- apps/admin/app/(all)/(dashboard)/authentication/page.tsx
- apps/admin/.eslintrc.cjs
- apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx
- apps/admin/app/(all)/(dashboard)/general/page.tsx
- apps/live/package.json
- apps/admin/app/types/next-navigation.d.ts
- apps/admin/app/(all)/store.provider.tsx
- apps/admin/app/(all)/(home)/auth-banner.tsx
- apps/admin/app/(all)/(dashboard)/header.tsx
- apps/admin/app/(all)/(dashboard)/general/form.tsx
- apps/admin/app/(all)/(dashboard)/email/page.tsx
- apps/admin/app/(all)/(home)/page.tsx
- apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx
- apps/admin/core/components/authentication/github-config.tsx
- apps/admin/core/store/theme.store.ts
- apps/admin/app/(all)/(dashboard)/ai/page.tsx
- apps/admin/core/store/workspace.store.ts
- apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx
- apps/admin/app/(all)/(home)/auth-helpers.tsx
- apps/admin/.dockerignore
- apps/admin/app/compat/next/navigation.ts
- apps/admin/core/components/authentication/gitlab-config.tsx
- apps/admin/core/hooks/store/use-theme.tsx
- apps/admin/core/store/instance.store.ts
- apps/admin/styles/globals.css
- apps/admin/app/(all)/(dashboard)/general/intercom.tsx
- apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx
- apps/admin/react-router.config.ts
- apps/admin/server.mjs
- apps/admin/app/providers.tsx
- apps/admin/app/types/next-image.d.ts
- apps/admin/core/utils/public-asset.ts
- apps/admin/core/components/authentication/password-config-switch.tsx
- apps/admin/app/compat/next/image.tsx
- apps/admin/core/components/common/logo-spinner.tsx
- apps/admin/core/components/authentication/google-config.tsx
- apps/admin/core/components/common/controller-input.tsx
- apps/admin/app/(all)/(dashboard)/workspace/page.tsx
- apps/admin/app/compat/next/link.tsx
- apps/web/app/(all)/layout.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-02T08:14:49.260Z
Learnt from: sriramveeraghanta
PR: makeplane/plane#7697
File: apps/web/app/(all)/[workspaceSlug]/(projects)/header.tsx:12-13
Timestamp: 2025-09-02T08:14:49.260Z
Learning: The star-us-link.tsx file in apps/web/app/(all)/[workspaceSlug]/(projects)/ already has "use client" directive at the top, making it a proper Client Component for hook usage.
Applied to files:
apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
📚 Learning: 2025-10-09T20:43:07.746Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/core/components/new-user-popup.tsx:4-6
Timestamp: 2025-10-09T20:43:07.746Z
Learning: The `next-themes` library is React-compatible and can be used outside of Next.js applications. It's not Next.js-specific despite its name.
Applied to files:
apps/admin/core/components/new-user-popup.tsx
📚 Learning: 2025-10-09T20:42:31.804Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.804Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Applied to files:
apps/admin/app/(all)/(dashboard)/ai/form.tsxapps/admin/app/(all)/(home)/sign-in-form.tsxapps/admin/app/(all)/(dashboard)/image/form.tsxapps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsxapps/admin/app/(all)/(dashboard)/authentication/github/form.tsxapps/admin/core/components/instance/failure.tsxapps/admin/app/(all)/(dashboard)/sidebar.tsxapps/admin/core/components/common/banner.tsxapps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx
🧬 Code graph analysis (5)
apps/admin/core/components/new-user-popup.tsx (1)
apps/admin/core/hooks/store/use-theme.tsx (1)
useTheme(6-10)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (3)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (3)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)
apps/admin/vite.config.ts (1)
apps/admin/server.mjs (1)
__dirname(8-8)
apps/admin/app/root.tsx (1)
apps/admin/app/providers.tsx (1)
AppProviders(19-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript)
🔇 Additional comments (33)
apps/admin/core/hooks/store/use-workspace.tsx (1)
4-4: LGTM! Proper use of type-only import.The change to
import typeis correct sinceIWorkspaceStoreis only used for type annotation. This improves build optimization by ensuring the type is erased at compile time.apps/admin/core/components/common/banner.tsx (1)
1-1: LGTM! Type-only import for FC.Converting the
FCimport to a type-only import is a beneficial refactor. SinceFCis exclusively used for type annotation (line 9) and React types are globally available in this codebase via TypeScript configuration, this change improves tree-shaking and makes the type-only usage explicit.Based on learnings.
packages/editor/package.json (1)
21-22: LGTM! Dual export paths for CSS styles.The addition of
"./styles.css"as an alias to the existing"./styles"export provides flexibility for consumers to import using either path. Both resolve to the same CSS file, and the change is consistent with the corresponding update intsdown.config.ts.packages/editor/tsdown.config.ts (1)
11-11: LGTM! Consistent export mapping added.The new
"./styles.css"export mapping aligns with the corresponding change inpackage.json, ensuring that both the package exports and the build configuration recognize the dual import paths for the CSS styles.packages/propel/package.json (1)
164-167: CSS export additions LGTMThanks for adding the
.cssaliases—this lines up with the newtsdownmappings so downstream apps can import the compiled styles without guessing extensions.apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx (2)
1-1: LGTM! Import changes align with modern React patterns.The addition of
Fragmentto the import statement is correct and necessary for the directFragmentusage throughout the component. The modern JSX transform eliminates the need for a default React import.
64-80: LGTM! Fragment usage is consistent and correct.All instances of
as={Fragment}are correct and consistent with the Fragment import added at line 1. This improves readability by using the direct imported reference instead of the namespace qualifier.apps/admin/core/store/user.store.ts (1)
3-8: LGTM! Correct type-only import separation.The refactor correctly separates type-only imports (
TUserStatus,IUser,CoreRootStore) from the value import (EUserStatus). This follows TypeScript best practices and improves bundle size by ensuring types are erased at compile time.apps/admin/core/store/root.store.ts (1)
3-10: LGTM! Proper type-only import separation.The refactor correctly separates concrete store classes (value imports) from their interfaces (type-only imports). This pattern is consistently applied across all four store types and follows TypeScript best practices.
apps/admin/core/hooks/store/use-user.tsx (1)
4-4: LGTM! Correct type-only import.The change to
import typeis appropriate sinceIUserStoreis only used as a return type annotation.apps/admin/app/(all)/(dashboard)/sidebar.tsx (1)
3-3: LGTM! The removal of FC type annotation is consistent with the migration.The removal of the
FCtype annotation and simplification to a plain function wrapped byobserveraligns with the broader refactor across the admin UI components. The runtime behavior remains unchanged, and the component logic is correct.Based on learnings: React types are globally available in this codebase through TypeScript configuration, so the FC type could still be used without importing if needed, but removing it is a valid stylistic choice.
Also applies to: 14-14
apps/admin/core/components/new-user-popup.tsx (1)
1-1: The "use client" directive is still present despite being marked as addressed.This line was flagged in a previous review and marked as "✅ Addressed in commits f2539c5 to 466aa32," but the directive still appears in the current code. Please verify whether the fix was applied or if this file needs to be updated to remove the Next.js-specific directive as part of the React Router migration.
apps/admin/core/components/common/empty-state.tsx (1)
10-10: LGTM! Type improvement enhances type safety.Changing the
imageprop type fromanytostringimproves type safety and accurately reflects how the prop is used (as an image URL).packages/eslint-config/package.json (1)
12-20: Verify Node/TypeScript baselines for @typescript-eslint v8
- Node engine ≥22.18.0 satisfies the ≥18.18 requirement.
- TypeScript is referenced via “catalog:” in package manifests—ensure the resolved TS version is ≥5.5.
packages/i18n/package.json (2)
35-38: Thanks for moving@types/node; double-check catalog entry.Relocating
@types/nodeinto devDependencies matches our pattern. Just ensure the catalog actually defines this reference so installs don’t fail.
27-30: Confirm CJS build doesn’t require the ESM-onlylodash-es.
Runnpm run buildand inspectdist/index.jsfor any barerequire("lodash-es"). If it’s still present (causingERR_REQUIRE_ESM), either bundlelodash-esinto the CJS output or switch the CJS export to uselodashinstead.apps/admin/core/components/authentication/email-config-switch.tsx (1)
6-6: LGTM! Type-only import is correct.Converting to
import typeis appropriate here sinceTInstanceAuthenticationMethodKeysis only used as a type annotation on line 14, not at runtime. This follows TypeScript best practices and aligns with the broader pattern of type-only imports across the admin app migration.apps/admin/core/components/instance/loading.tsx (2)
4-5: LGTM: Vite asset imports are correctly configured.The
?urlsuffix is the correct Vite syntax for importing assets as URL strings. This ensures the imported values are public URL strings that can be used directly as image sources.However, ensure the Image component issue (Line 1) is resolved for the migration to work correctly.
1-1: Next/Image compatibility shim verified
A shim exists in apps/admin/app/compat/next/image.tsx (with types in apps/admin/app/types/next-image.d.ts), so thenext/imageimport is safe to keep.apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx (1)
3-3: LGTM! Typing update aligns with project-wide refactor.The removal of the
FCimport and switch toReact.FCannotation is consistent with the broader typing standardization across the admin app. Since React types are globally available in this codebase, theReact.FCusage is correct without an explicit namespace import.Also applies to: 37-37
apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx (1)
2-2: Excellent typing and import hygiene.The refactor to type-only imports for configuration types and form field types (lines 11, 17, 19) follows TypeScript best practices by enabling better tree-shaking and making the distinction between types and runtime values explicit. The component signature update to
React.FC<Props>is consistent with the project-wide pattern.Also applies to: 11-11, 16-19, 29-29
apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx (1)
1-1: Authentication form typing refactor complete.The GitLab form now matches the typing structure of the GitHub and Google authentication forms. The type-only imports (lines 9, 15, 17) and
React.FCsignature (line 27) complete the consistent pattern across all authentication configuration forms.Also applies to: 9-9, 14-17, 27-27
packages/logger/package.json (1)
31-31:@types/[email protected]confirmed published Version 4.17.23 exists on npm and can be merged as-is.apps/admin/core/components/instance/instance-not-ready.tsx (2)
7-7: LGTM! Correct Vite asset import syntax.The
?urlsuffix is the correct Vite syntax for importing static asset URLs. This change properly aligns with the migration to Vite.
9-9: React.FC usage is valid without explicit import
Modern JSX transform and global React types (via@types/react) makeReact.FCand JSX available without importing React.Likely an incorrect or invalid review comment.
apps/admin/core/components/instance/failure.tsx (3)
8-9: LGTM! Asset imports correctly updated for Vite.The migration from public SVG paths to
app/assetswith the?urlquery parameter is the correct approach for Vite's static asset handling. This ensures the SVG files are imported as URL strings compatible with Vite's build system.Based on learnings
11-11: The past review comment is incorrect.The previous comment suggesting to import React is not applicable to this codebase. React types (including
React.FC) are globally available through TypeScript configuration, and the modern JSX transform means React imports are not required for type references.Based on learnings
1-4: Confirm or replace Next.js-specific imports
This component still importsnext/imageandnext-themes, but I didn’t find a clear shim or alias for them. Confirm that@bprogress/next(or another layer) provides replacements and that import paths have been updated accordingly. If no shim exists, replace with framework-agnostic alternatives (e.g.,<img>tags, a generic theme provider).packages/decorators/package.json (1)
27-27: Confirmed @types/[email protected] is published. Installation will succeed; no changes needed.apps/admin/app/(all)/(home)/sign-in-form.tsx (1)
7-8: LGTM! Type-only import separation is a best practice.Splitting type imports from value imports improves tree-shaking and helps prevent circular dependency issues in TypeScript.
apps/admin/ce/components/authentication/authentication-modes.tsx (3)
6-10: LGTM!Using
import typefor type-only imports is a TypeScript best practice. This ensures types are stripped at build time and don't affect the runtime bundle.
2-2:next/imageimport is correctly shimmed—no action required
The Vite config aliases"next/image"toapp/compat/next/image.tsxwith corresponding types inapps/admin/app/types/next-image.d.ts, so no replacement is needed.Likely an incorrect or invalid review comment.
13-18: Approve Vite asset imports
Verified all six logos exist in apps/admin/app/assets/logos and the?urlimports are correct.
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the Next.js "use client" directive.
This directive is specific to Next.js and has no effect in React Router. Since this PR migrates from Next.js to React Router, the directive should be removed.
Apply this diff to remove the directive:
-"use client";
-
import { useEffect, useRef } from "react";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "use client"; | |
| import { useEffect, useRef } from "react"; |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/sidebar.tsx around line 1, remove the
Next.js-specific "use client" directive at the top of the file; this directive
has no effect under React Router—delete that line so the file no longer contains
the "use client" pragma and ensure there are no leftover blank lines at the top.
| "use client"; | ||
|
|
||
| import { FC, useEffect, useMemo, useState } from "react"; | ||
| import { useEffect, useMemo, useState } from "react"; | ||
| import { useSearchParams } from "next/navigation"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify Next.js dependencies in React Router migration.
The file retains Next.js-specific code:
- Line 1:
"use client"directive (Next.js App Router feature) - Line 4:
useSearchParamsimported fromnext/navigation
In a React Router app, useSearchParams should be imported from react-router-dom. Confirm whether Next.js dependencies will remain available or if these imports should be updated to React Router equivalents.
Run this script to check if other migrated files still reference next/navigation:
🏁 Script executed:
#!/bin/bash
# Description: Check for remaining next/navigation imports in admin app files
# Search for next/navigation imports in the admin app directory
rg --type=typescript --type=tsx -n "from ['\"]next/navigation['\"]" apps/admin/
# Also check for "use client" directives
rg --type=typescript --type=tsx -n "^['\"]use client['\"]" apps/admin/Length of output: 69
🏁 Script executed:
#!/bin/bash
# Search for remaining Next.js-specific imports and directives in admin app
rg -n "from ['\"]next/navigation['\"]" apps/admin/ -g '*.ts' -g '*.tsx'
rg -n '^"use client"' apps/admin/ -g '*.ts' -g '*.tsx'Length of output: 4126
Replace Next.js-specific code in the admin app with React Router equivalents.
- Remove all
"use client"directives unless required by your bundler; Vite/React Router doesn’t use this. - Replace imports from
next/navigation(useSearchParams,useRouter,usePathname) with React Router hooks fromreact-router-dom(e.g.useSearchParams,useNavigate,useLocation,useParams). - Audit
apps/admin/for any remainingnext/navigationreferences and update them accordingly.
| import { observer } from "mobx-react"; | ||
| import useSWR from "swr"; | ||
| // hooks | ||
| import { useInstance } from "@/hooks/store"; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React import for React.FC and React.PropsWithChildren.
The code uses React.FC and React.PropsWithChildren on line 6 but does not import React. This will cause a ReferenceError at runtime.
Add the React import:
+import React from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
import { useInstance } from "@/hooks/store";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { observer } from "mobx-react"; | |
| import useSWR from "swr"; | |
| // hooks | |
| import { useInstance } from "@/hooks/store"; | |
| import React from "react"; | |
| import { observer } from "mobx-react"; | |
| import useSWR from "swr"; | |
| // hooks | |
| import { useInstance } from "@/hooks/store"; |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/instance.provider.tsx around lines 1 to 5, the file uses
React.FC and React.PropsWithChildren but does not import React, causing a
runtime ReferenceError; add an import for React at the top of the file (e.g.,
import React from "react" or import * as React from "react") so those
type/namespace references resolve, keeping existing imports intact.
| }; | ||
|
|
||
| export const InstanceProvider: FC<InstanceProviderProps> = observer((props) => { | ||
| export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect observer generic usage with React.FC.
The pattern observer<React.FC<React.PropsWithChildren>> is incorrect. The observer function's generic parameter expects a props type, not a component type like React.FC<Props>. This may cause type errors or unexpected behavior.
Use one of these correct patterns:
Option 1 (Recommended): Type annotation on the left side:
-export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => {
+export const InstanceProvider: React.FC<React.PropsWithChildren> = observer((props) => {Option 2: Pass props type to observer and annotate the parameter:
-export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => {
+export const InstanceProvider = observer<React.PropsWithChildren>((props: React.PropsWithChildren) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => { | |
| export const InstanceProvider: React.FC<React.PropsWithChildren> = observer((props) => { |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/instance.provider.tsx around line 6, the current use of
observer with a component type generic
(observer<React.FC<React.PropsWithChildren>>) is incorrect because observer
expects a props type, not a component type; remove the generic from observer and
either (a) annotate the component variable with the React.FC/PropsWithChildren
type and pass the plain function into observer, or (b) pass the props type to
observer and annotate the function parameter (e.g., observer((props: YourProps)
=> ...)); update the declaration accordingly so observer receives only a props
type or no generic and the component itself is typed on the left-hand side.
| } | ||
|
|
||
| export const UserProvider: FC<IUserProvider> = observer(({ children }) => { | ||
| export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix observer generic and missing React import.
observer<React.FC<React.PropsWithChildren>> is invalid: the generic expects the props object (P), not a component type, so TypeScript will infer P as the React.FC constructor (which doesn’t extend object). This will break the build. Additionally, React isn’t imported, so the React. namespace is undefined. Drop the generic and type the props explicitly, e.g.:
-import { useEffect } from "react";
+import { type PropsWithChildren, useEffect } from "react";
…
-export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => {
+export const UserProvider = observer(({ children }: PropsWithChildren) => {or declare type UserProviderProps = PropsWithChildren; and use that. This keeps observer happy and removes the need for a React namespace import.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => { | |
| import { type PropsWithChildren, useEffect } from "react"; | |
| // … other imports | |
| export const UserProvider = observer(({ children }: PropsWithChildren) => { | |
| // … component implementation | |
| }); |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/user.provider.tsx around line 9, the observer is
incorrectly given a component-type generic and React is not imported; remove the
incorrect generic from observer, explicitly type the props as PropsWithChildren
(or declare type UserProviderProps = PropsWithChildren) and add an import for
PropsWithChildren from 'react', e.g. change to observer(({ children }:
PropsWithChildren) => { ... }) or use the declared UserProviderProps in the
parameter, which fixes the generic misuse and provides the missing React type
import.
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete Next.js to React Router migration.
The file contains Next.js-specific code that must be migrated:
- Line 1: The
"use client"directive is a Next.js feature and should be removed in a Vite + React Router app. - Line 4: The
useSearchParamsimport from"next/navigation"must be replaced with React Router's equivalent.
Apply this diff to complete the migration:
-"use client";
-
import { useEffect, useMemo, useState } from "react";
-import { useSearchParams } from "next/navigation";
+import { useSearchParams } from "react-router";Also applies to: 4-4
🤖 Prompt for AI Agents
In apps/admin/core/components/instance/setup-form.tsx lines 1 and 4, remove the
Next.js "use client" directive and replace the Next-specific import of
useSearchParams from "next/navigation" with React Router's hook by importing
useSearchParams from "react-router-dom"; update any surrounding import list
accordingly so the component uses the React Router hook (no other runtime
changes required if usages already follow the standard useSearchParams API).
| "use client"; | ||
|
|
||
| import { FC, useEffect, useMemo, useState } from "react"; | ||
| import { useEffect, useMemo, useState } from "react"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React import for React.FC usage.
Line 56 uses React.FC but the React namespace is never imported, causing a runtime error.
Apply this diff to import React:
-import { useEffect, useMemo, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";Alternatively, import and use FC directly:
-import { useEffect, useMemo, useState } from "react";
+import { type FC, useEffect, useMemo, useState } from "react";Then update line 56:
-export const InstanceSetupForm: React.FC = (props) => {
+export const InstanceSetupForm: FC = () => {🤖 Prompt for AI Agents
In apps/admin/core/components/instance/setup-form.tsx around lines 3 and 56, the
file uses React.FC at line 56 but never imports the React namespace; either add
a top import for React (e.g. import React from "react") at the top of the file
or import FC directly (e.g. import { FC } from "react"), then update the
component declaration at line 56 to use the matching identifier (React.FC if you
imported React, or FC if you imported FC) so the type reference resolves.
| HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ | ||
| CMD curl -fsS http://127.0.0.1:3000/ >/dev/null || exit 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Install curl before using it in the healthcheck.
nginx:1.27-alpine does not ship with curl, so the healthcheck exits 127 immediately and marks the container unhealthy. Add curl (or swap to an installed tool) in the production stage before the healthcheck runs.
FROM nginx:1.27-alpine AS production
+
+RUN apk add --no-cache curl🤖 Prompt for AI Agents
In apps/admin/Dockerfile.admin around lines 83-84, the HEALTHCHECK uses curl but
the base image nginx:1.27-alpine doesn’t include curl so the check fails with
exit 127; install curl (e.g., apk add --no-cache curl) in the production stage
before the HEALTHCHECK line or replace the check with a tool already present
(like wget or nc) and ensure the package install is in the same stage so the
binary exists at healthcheck runtime.
| set_real_ip_from 0.0.0.0/0; | ||
| real_ip_recursive on; | ||
| real_ip_header X-Forward-For; | ||
| limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; | ||
|
|
||
| access_log /dev/stdout; | ||
| error_log /dev/stderr; | ||
|
|
||
| server { | ||
| listen 3000; | ||
|
|
||
| location / { | ||
| root /usr/share/nginx/html; | ||
| index index.html index.htm; | ||
| try_files $uri $uri/ /index.html; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not trust all client IPs & fix real_ip_header
set_real_ip_from 0.0.0.0/0; tells Nginx to trust every client-supplied X-Forwarded-For, so any attacker can spoof their IP and bypass rate limits/logging. Restrict this to your real proxy network or drop it entirely. Also, the header name is misspelled—use X-Forwarded-For.
- set_real_ip_from 0.0.0.0/0;
- real_ip_recursive on;
- real_ip_header X-Forward-For;
+ # set_real_ip_from <trusted proxy CIDR>;
+ real_ip_recursive on;
+ real_ip_header X-Forwarded-For;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| set_real_ip_from 0.0.0.0/0; | |
| real_ip_recursive on; | |
| real_ip_header X-Forward-For; | |
| limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; | |
| access_log /dev/stdout; | |
| error_log /dev/stderr; | |
| server { | |
| listen 3000; | |
| location / { | |
| root /usr/share/nginx/html; | |
| index index.html index.htm; | |
| try_files $uri $uri/ /index.html; | |
| } | |
| # set_real_ip_from <trusted proxy CIDR>; | |
| real_ip_recursive on; | |
| real_ip_header X-Forwarded-For; | |
| limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; | |
| access_log /dev/stdout; | |
| error_log /dev/stderr; | |
| server { | |
| listen 3000; | |
| location / { | |
| root /usr/share/nginx/html; | |
| index index.html index.htm; | |
| try_files $uri $uri/ /index.html; | |
| } |
🤖 Prompt for AI Agents
In apps/admin/nginx/nginx.conf around lines 12 to 27, the config currently
trusts all client IPs and misspells the header: change or remove the overly
broad set_real_ip_from 0.0.0.0/0 and instead list only your trusted proxy
CIDR(s) (or omit the directive if there is no trusted proxy), and correct the
header name from X-Forward-For to X-Forwarded-For; keep real_ip_recursive on if
you need the left-most non-proxy IP, and ensure any rate-limiting uses the
trusted remote address variable after these changes.
| app.use( | ||
| "/api", | ||
| createProxyMiddleware({ | ||
| target: NEXT_PUBLIC_API_BASE_URL, | ||
| changeOrigin: true, | ||
| secure: false, | ||
| pathRewrite: (path: string) => | ||
| NORMALIZED_API_BASE_PATH === "/api" ? path : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH), | ||
| }) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore TLS verification on the API proxy.
secure: false disables certificate validation for every proxied request, leaving the admin API traffic vulnerable to MITM attacks in production. Please let http-proxy-middleware verify TLS (or only disable it for clearly scoped local targets).
app.use(
"/api",
createProxyMiddleware({
target: NEXT_PUBLIC_API_BASE_URL,
changeOrigin: true,
- secure: false,
+ secure: !/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(NEXT_PUBLIC_API_BASE_URL),
pathRewrite: (path: string) =>
NORMALIZED_API_BASE_PATH === "/api" ? path : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH),
})
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| app.use( | |
| "/api", | |
| createProxyMiddleware({ | |
| target: NEXT_PUBLIC_API_BASE_URL, | |
| changeOrigin: true, | |
| secure: false, | |
| pathRewrite: (path: string) => | |
| NORMALIZED_API_BASE_PATH === "/api" ? path : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH), | |
| }) | |
| ); | |
| app.use( | |
| "/api", | |
| createProxyMiddleware({ | |
| target: NEXT_PUBLIC_API_BASE_URL, | |
| changeOrigin: true, | |
| secure: !/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(NEXT_PUBLIC_API_BASE_URL), | |
| pathRewrite: (path: string) => | |
| NORMALIZED_API_BASE_PATH === "/api" | |
| ? path | |
| : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH), | |
| }) | |
| ); |
🤖 Prompt for AI Agents
In apps/admin/server/app.ts around lines 27 to 36, the proxy is configured with
secure: false which disables TLS certificate validation for all proxied
requests; change this to enable verification by setting secure: true for
production (or remove the option) and only disable it for explicit local targets
— e.g., compute a boolean like isLocalTarget by parsing NEXT_PUBLIC_API_BASE_URL
and set secure: isLocalTarget ? false : true (or secure: !isLocalTarget) so TLS
is verified except when the target host is localhost/127.0.0.1 or a clearly
scoped development URL.
4ffa5ac to
b18aa45
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
14-95: Fixobservergeneric usage
observer<React.FC<Route.ComponentProps>>sets the props type toReact.FC<Route.ComponentProps>, so the wrapped component no longer type-checks againstRoute.ComponentProps. Type the variable (or supply the props type) instead:-const InstanceEmailPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceEmailPage: React.FC<Route.ComponentProps> = observer(() => {This keeps the observer wrapper and restores proper route-props typing.
apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
5-67: Replace Next.jsImagewith a standard<img>(React Router build will fail).
next/imageisn’t available in the new Vite/React Router admin app, so bundling this page will crash with “Cannot resolve module 'next/image'”. Swap the import and usage for a plain<img>(or another router-compatible image helper).-import Image from "next/image"; ... - icon={<Image src={GoogleLogo} height={24} width={24} alt="Google Logo" />} + icon={<img src={GoogleLogo} height={24} width={24} alt="Google Logo" />}
♻️ Duplicate comments (17)
apps/admin/app/(all)/(home)/auth-helpers.tsx (1)
1-2: Next.js imports remain - issue not addressed.The Next.js-specific imports from "next/image" and "next/link" are still present despite being flagged in a previous review. These are incompatible with the React Router migration.
As noted in the previous review, replace these with React Router equivalents and update all corresponding component usages (Link href → to, Image → img).
apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx (1)
3-3: Critical: Missing React namespace import (unfixed from previous review).The component uses
React.FC<IInstanceEmailForm>on Line 33 but the React namespace is not imported, causing aReferenceError: React is not definedat runtime.This issue was flagged in a previous review but remains unfixed.
Add the React namespace import:
"use client"; +import React from "react"; import { useMemo, useState } from "react";apps/admin/core/components/common/logo-spinner.tsx (2)
3-4: Vite asset imports need alias configuration to function.The
?urlsuffix is correct for Vite static asset handling. However, the@/apppath alias requires configuration in bothvite.config.tsandtsconfig.jsonas noted in the previous review.
1-2: Critical: Next.js dependencies must be removed for Vite compatibility.This file still imports
next/imageandnext-themes, which will cause runtime failures in the React Router + Vite environment. The previous review comment already identified this issue and provided the necessary fixes.apps/admin/app/(all)/user.provider.tsx (1)
9-9: Already flagged: Fix observer generic and missing React import.This issue was already identified in a previous review. The
observer<React.FC<React.PropsWithChildren>>syntax is invalid, and theReactnamespace is not imported. Please refer to the previous review comment for the complete fix.apps/admin/core/components/instance/instance-not-ready.tsx (2)
1-1: Duplicate: "use client" directive still present.This issue was already flagged in a previous review. The Next.js-specific "use client" directive must be removed for React Router + Vite compatibility.
3-4: Duplicate: Next.js imports still present.This issue was already flagged in a previous review. The Next.js
ImageandLinkimports (and their usage on lines 14 and 21) must be replaced with React Router and standard HTML equivalents.apps/admin/app/(all)/(dashboard)/layout.tsx (1)
5-5: Critical: Fix the Next.js router import (still unresolved from previous reviews).This critical issue was flagged in previous reviews but remains unfixed. Line 5 still imports
useRouterfrom "next/navigation", which will cause runtime errors in a React Router context. Thereplacemethod on line 19 is from the Next.js router API and won't work correctly.Apply this diff to fix the import and usage:
-import { useRouter } from "next/navigation"; +import { useNavigate } from "react-router"; import { Outlet } from "react-router";Then update the router usage:
- const { replace } = useRouter(); + const navigate = useNavigate();- if (isUserLoggedIn === false) replace("/"); + if (isUserLoggedIn === false) navigate("/", { replace: true });- }, [replace, isUserLoggedIn]); + }, [navigate, isUserLoggedIn]);Also applies to: 19-19, 24-25
apps/admin/core/components/authentication/google-config.tsx (1)
4-52: Swap outnext/linkfor React Router’sLink.
next/linkwon’t exist once this admin app runs under Vite + React Router, so any navigation here will explode at runtime. ImportLinkfrom the router and maphref→to.-import Link from "next/link"; +import { Link } from "react-router-dom"; ... - <Link href="/authentication/google" className={cn(getButtonStyling("link-primary", "md"), "font-medium")}> + <Link to="/authentication/google" className={cn(getButtonStyling("link-primary", "md"), "font-medium")}> ... - <Link - href="/authentication/google" + <Link + to="/authentication/google"apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
19-19:observergeneric must receive the props shape, not the component type.Passing
React.FC<Route.ComponentProps>makes MobX expect a prop whose value is a component function, so the file won’t type-check. Annotate the component itself and callobserverwithout that generic.-const InstanceGoogleAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceGoogleAuthenticationPage: React.FC<Route.ComponentProps> = observer(() => {apps/admin/app/(all)/instance.provider.tsx (1)
6-6: Provide the props type toobserver, not the component type.
observer<React.FC<React.PropsWithChildren>>fails because MobX now thinks the component receives a prop named after a component. Pass the props shape instead (or annotate on the left) to restore correct typing.-export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => { - const { children } = props; +export const InstanceProvider = observer<React.PropsWithChildren>(({ children }) => {apps/admin/ce/components/authentication/authentication-modes.tsx (1)
101-121: Restore explicit component typing and import React forReact.FC.The component is still declared with
observer<React.FC<TAuthenticationModeProps>>((props) => …), butobserverexpects the component function as its argument, not a generic typed arrow. This pattern also references theReactnamespace without importing it, causing TS to error (Cannot find name 'React'). Please revert to the prevailing pattern:-import { observer } from "mobx-react"; +import { observer } from "mobx-react"; +import type { FC } from "react"; … -export const AuthenticationModes = observer<React.FC<TAuthenticationModeProps>>((props) => { +export const AuthenticationModes: FC<TAuthenticationModeProps> = observer((props) => {This matches the rest of the codebase and fixes the missing type import.
apps/admin/app/(all)/(dashboard)/ai/page.tsx (1)
12-44: Import React type and revert to standard observer wrapping.
observer<React.FC<Route.ComponentProps>>referencesReactwithout an import and misuses the generic. Follow the established pattern instead:-import type { Route } from "./+types/page"; +import type { FC } from "react"; +import type { Route } from "./+types/page"; … -const InstanceAIPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceAIPage: FC<Route.ComponentProps> = observer(() => {This fixes the missing namespace and aligns with other pages.
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
19-104: Add React type import and use the observer wrapper consistently.The component still uses
observer<React.FC<Route.ComponentProps>>without importing theReactnamespace. Update it to the established pattern:-import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url"; +import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url"; +import type { FC } from "react"; … -const InstanceGitlabAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceGitlabAuthenticationPage: FC<Route.ComponentProps> = observer(() => {This resolves the missing type import and keeps typings consistent.
apps/admin/server.mjs (1)
15-70: Guard against missing/invalid ports before callingapp.listen.When
NEXT_PUBLIC_ADMIN_BASE_URLomits the port (e.g.,https://admin.example.com),parsedBaseUrl.portis"";Number.parseInt("", 10)yieldsNaN, soapp.listen(PORT, …)throws immediately. Preferprocess.env.PORTwhen valid, fall back to a parsedparsedBaseUrl.portonly if present, otherwise default:-const PORT = Number.parseInt(parsedBaseUrl.port, 10); +const envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined; +const urlPort = parsedBaseUrl.port ? Number.parseInt(parsedBaseUrl.port, 10) : undefined; +const PORT = [envPort, urlPort, 3001].find((value) => Number.isFinite(value))!;This keeps the server booting across environments.
apps/admin/Dockerfile.admin (1)
76-86: Installcurlbefore running the healthcheck.
nginx:1.27-alpinedoesn’t ship withcurl, so the healthcheck exits with 127 and marks the container unhealthy. Addapk add --no-cache curlin this stage before theHEALTHCHECK.FROM nginx:1.27-alpine AS production + +RUN apk add --no-cache curlWithout it, the healthcheck never succeeds.
apps/admin/vite.config.ts (1)
1-43: Define__dirnamefor this ESM config.You’re using
__dirnameon Line 40, but in an ES module (the admin package is"type": "module"), that identifier is undefined. Vite will throw aReferenceErroras soon as it evaluates the config. ImportfileURLToPathfromnode:urland compute__dirnameexplicitly before using it.-import path from "node:path"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; ... -const publicEnv = PUBLIC_ENV_KEYS.reduce<Record<string, string>>((acc, key) => { +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const publicEnv = PUBLIC_ENV_KEYS.reduce<Record<string, string>>((acc, key) => {
🧹 Nitpick comments (4)
apps/admin/app/types/react-router-virtual.d.ts (1)
1-5: Use official React Router types forvirtual:react-router/server-build
React Router v7.4.0+ ships TypeScript definitions for this virtual module. Remove theanydeclaration and import the providedServerBuildtype:declare module "virtual:react-router/server-build" { import type { ServerBuild } from "react-router"; const build: ServerBuild; export default build; }apps/admin/app/compat/next/image.tsx (1)
12-12: Consider requiring alt text or using a more descriptive default.The alt attribute defaults to an empty string, which may create accessibility issues if call sites don't provide alt text. While this matches the minimal shim goal, consider either:
- Making alt required in NextImageProps
- Using a placeholder like "Image" as the default
- Adding a console warning when alt is not provided
Apply this diff to make alt required:
type NextImageProps = React.ImgHTMLAttributes<HTMLImageElement> & { src: string; + alt: string; }; -const Image: React.FC<NextImageProps> = ({ src, alt = "", ...rest }) => <img src={src} alt={alt} {...rest} />; +const Image: React.FC<NextImageProps> = ({ src, alt, ...rest }) => <img src={src} alt={alt} {...rest} />;apps/admin/package.json (2)
66-73: Consider removing low-level transitive dependencies from direct dependencies.Lines 66-73 list several low-level packages (token-types, strtok3, @tokenizer/inflate, uint8array-extras, fflate, @borewit/text-codec, ieee754, debug) that appear to be transitive dependencies. These are typically managed by your actual dependencies (like audio/video/file parsing libraries) and shouldn't be listed as direct dependencies unless you're using them directly in your code.
To verify these are not used directly:
#!/bin/bash # Search for direct imports of these packages rg -nP --type=ts --type=tsx 'from ["\x27](token-types|strtok3|@tokenizer/inflate|uint8array-extras|fflate|@borewit/text-codec|ieee754|debug)["\x27]' apps/admin
88-89: Verify Node.js version meets Vite 7 requirements.Vite 7.1.7 requires Node.js 20.19+ or 22.12+. Ensure your development environment, CI/CD pipeline, and deployment environment meet this requirement.
Based on learnings.
Check the Node.js version requirement in your CI configuration and document it in the README if not already present.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (21)
apps/admin/app/assets/favicon/apple-touch-icon.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-16x16.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-32x32.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon.icois excluded by!**/*.icoapps/admin/app/assets/images/logo-spinner-dark.gifis excluded by!**/*.gifapps/admin/app/assets/images/logo-spinner-light.gifis excluded by!**/*.gifapps/admin/app/assets/images/plane-takeoff.pngis excluded by!**/*.pngapps/admin/app/assets/instance/instance-failure-dark.svgis excluded by!**/*.svgapps/admin/app/assets/instance/instance-failure.svgis excluded by!**/*.svgapps/admin/app/assets/logos/github-black.pngis excluded by!**/*.pngapps/admin/app/assets/logos/github-white.pngis excluded by!**/*.pngapps/admin/app/assets/logos/gitlab-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/google-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/oidc-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/saml-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-dark.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-light.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern-dark.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern.svgis excluded by!**/*.svgapps/admin/public/instance/plane-takeoff.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (107)
.dockerignore(1 hunks).gitignore(1 hunks)apps/admin/.dockerignore(1 hunks)apps/admin/.eslintrc.cjs(1 hunks)apps/admin/.eslintrc.js(0 hunks)apps/admin/Dockerfile.admin(1 hunks)apps/admin/app/(all)/(dashboard)/ai/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/ai/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/ai/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx(3 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/email/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx(4 hunks)apps/admin/app/(all)/(dashboard)/general/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/intercom.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/general/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/header.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/form.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/image/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/layout.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/sidebar.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx(1 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/workspace/page.tsx(2 hunks)apps/admin/app/(all)/(home)/auth-banner.tsx(1 hunks)apps/admin/app/(all)/(home)/auth-helpers.tsx(2 hunks)apps/admin/app/(all)/(home)/layout.tsx(1 hunks)apps/admin/app/(all)/(home)/page.tsx(2 hunks)apps/admin/app/(all)/(home)/sign-in-form.tsx(2 hunks)apps/admin/app/(all)/instance.provider.tsx(1 hunks)apps/admin/app/(all)/store.provider.tsx(2 hunks)apps/admin/app/(all)/user.provider.tsx(1 hunks)apps/admin/app/compat/next/image.tsx(1 hunks)apps/admin/app/compat/next/link.tsx(1 hunks)apps/admin/app/compat/next/navigation.ts(1 hunks)apps/admin/app/layout.tsx(0 hunks)apps/admin/app/providers.tsx(1 hunks)apps/admin/app/root.tsx(1 hunks)apps/admin/app/routes.ts(1 hunks)apps/admin/app/types/next-image.d.ts(1 hunks)apps/admin/app/types/next-link.d.ts(1 hunks)apps/admin/app/types/next-navigation.d.ts(1 hunks)apps/admin/app/types/react-router-virtual.d.ts(1 hunks)apps/admin/ce/components/authentication/authentication-modes.tsx(2 hunks)apps/admin/core/components/authentication/authentication-method-card.tsx(1 hunks)apps/admin/core/components/authentication/email-config-switch.tsx(1 hunks)apps/admin/core/components/authentication/github-config.tsx(1 hunks)apps/admin/core/components/authentication/gitlab-config.tsx(1 hunks)apps/admin/core/components/authentication/google-config.tsx(1 hunks)apps/admin/core/components/authentication/password-config-switch.tsx(1 hunks)apps/admin/core/components/common/banner.tsx(1 hunks)apps/admin/core/components/common/controller-input.tsx(1 hunks)apps/admin/core/components/common/empty-state.tsx(1 hunks)apps/admin/core/components/common/logo-spinner.tsx(1 hunks)apps/admin/core/components/instance/failure.tsx(1 hunks)apps/admin/core/components/instance/instance-not-ready.tsx(1 hunks)apps/admin/core/components/instance/loading.tsx(1 hunks)apps/admin/core/components/instance/setup-form.tsx(2 hunks)apps/admin/core/components/new-user-popup.tsx(1 hunks)apps/admin/core/hooks/store/use-instance.tsx(1 hunks)apps/admin/core/hooks/store/use-theme.tsx(1 hunks)apps/admin/core/hooks/store/use-user.tsx(1 hunks)apps/admin/core/hooks/store/use-workspace.tsx(1 hunks)apps/admin/core/store/instance.store.ts(2 hunks)apps/admin/core/store/root.store.ts(1 hunks)apps/admin/core/store/theme.store.ts(1 hunks)apps/admin/core/store/user.store.ts(1 hunks)apps/admin/core/store/workspace.store.ts(1 hunks)apps/admin/core/utils/public-asset.ts(1 hunks)apps/admin/middleware.js(1 hunks)apps/admin/next-env.d.ts(0 hunks)apps/admin/next.config.js(0 hunks)apps/admin/nginx/nginx.conf(1 hunks)apps/admin/package.json(2 hunks)apps/admin/public/.well-known/appspecific/com.chrome.devtools.json(1 hunks)apps/admin/public/favicon/site.webmanifest(0 hunks)apps/admin/react-router.config.ts(1 hunks)apps/admin/server.mjs(1 hunks)apps/admin/server/app.ts(1 hunks)apps/admin/styles/globals.css(1 hunks)apps/admin/tsconfig.json(1 hunks)apps/admin/vite.config.ts(1 hunks)apps/live/package.json(1 hunks)apps/web/app/(all)/layout.tsx(1 hunks)apps/web/package.json(0 hunks)packages/decorators/package.json(1 hunks)packages/editor/package.json(1 hunks)packages/editor/tsdown.config.ts(1 hunks)packages/eslint-config/package.json(1 hunks)packages/i18n/package.json(1 hunks)packages/logger/package.json(1 hunks)packages/propel/package.json(2 hunks)
⛔ Files not processed due to max files limit (7)
- packages/propel/tsdown.config.ts
- packages/services/package.json
- packages/shared-state/package.json
- packages/ui/package.json
- packages/utils/package.json
- packages/utils/src/string.ts
- pnpm-workspace.yaml
💤 Files with no reviewable changes (15)
- apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx
- apps/admin/app/(all)/(dashboard)/ai/layout.tsx
- apps/admin/next.config.js
- apps/admin/app/layout.tsx
- apps/admin/app/(all)/(dashboard)/general/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx
- apps/admin/app/(all)/(dashboard)/image/layout.tsx
- apps/admin/app/(all)/(dashboard)/email/layout.tsx
- apps/admin/app/(all)/(dashboard)/workspace/layout.tsx
- apps/admin/public/favicon/site.webmanifest
- apps/admin/next-env.d.ts
- apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/layout.tsx
- apps/admin/.eslintrc.js
- apps/web/package.json
✅ Files skipped from review due to trivial changes (4)
- apps/admin/.dockerignore
- .dockerignore
- .gitignore
- apps/admin/public/.well-known/appspecific/com.chrome.devtools.json
🚧 Files skipped from review as they are similar to previous changes (40)
- apps/admin/core/utils/public-asset.ts
- apps/admin/core/components/common/empty-state.tsx
- apps/admin/core/store/workspace.store.ts
- apps/admin/core/components/common/banner.tsx
- apps/admin/app/(all)/store.provider.tsx
- apps/admin/core/store/root.store.ts
- apps/admin/app/(all)/(dashboard)/general/page.tsx
- apps/admin/core/components/authentication/authentication-method-card.tsx
- apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx
- apps/admin/app/(all)/(dashboard)/workspace/create/form.tsx
- packages/i18n/package.json
- apps/admin/app/(all)/(dashboard)/image/form.tsx
- apps/admin/tsconfig.json
- apps/admin/app/types/next-image.d.ts
- apps/admin/core/components/common/controller-input.tsx
- apps/admin/app/(all)/(dashboard)/general/form.tsx
- apps/admin/app/(all)/(dashboard)/authentication/page.tsx
- apps/admin/server/app.ts
- apps/admin/core/store/user.store.ts
- packages/editor/package.json
- apps/admin/core/components/authentication/github-config.tsx
- apps/admin/core/components/new-user-popup.tsx
- apps/admin/app/(all)/(home)/auth-banner.tsx
- apps/admin/app/(all)/(home)/page.tsx
- apps/admin/core/hooks/store/use-user.tsx
- apps/admin/app/types/next-navigation.d.ts
- packages/editor/tsdown.config.ts
- packages/eslint-config/package.json
- apps/admin/app/(all)/(dashboard)/header.tsx
- apps/admin/.eslintrc.cjs
- apps/admin/app/providers.tsx
- apps/admin/core/components/instance/setup-form.tsx
- apps/admin/app/(all)/(dashboard)/workspace/page.tsx
- apps/admin/app/(all)/(home)/sign-in-form.tsx
- apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx
- apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx
- apps/admin/react-router.config.ts
- apps/admin/core/store/theme.store.ts
- apps/admin/app/(all)/(dashboard)/sidebar.tsx
- apps/admin/core/components/instance/failure.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-09T20:42:31.804Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.804Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Applied to files:
apps/admin/app/(all)/(dashboard)/general/intercom.tsxapps/admin/app/(all)/(dashboard)/authentication/github/form.tsxapps/admin/app/(all)/(dashboard)/ai/form.tsxapps/admin/app/(all)/instance.provider.tsxapps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx
📚 Learning: 2025-10-09T22:12:26.397Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T22:12:26.397Z
Learning: When `types/react` is installed in a TypeScript project (which is standard for React + TypeScript codebases), React types (React.FC, React.ReactNode, React.ComponentProps, etc.) are globally available by design. These type annotations can and should be used without explicitly importing the React namespace. This is a TypeScript/DefinitelyTyped feature, not codebase-specific configuration.
Applied to files:
apps/admin/app/(all)/(dashboard)/general/intercom.tsxapps/admin/app/(all)/(dashboard)/ai/form.tsx
🧬 Code graph analysis (5)
apps/admin/app/(all)/(dashboard)/email/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/root.tsx (1)
apps/admin/app/providers.tsx (1)
AppProviders(19-32)
apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
meta(103-103)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (5)
apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx (1)
meta(104-104)apps/admin/app/(all)/(dashboard)/authentication/page.tsx (1)
meta(115-115)apps/admin/app/(all)/(dashboard)/email/page.tsx (1)
meta(95-95)apps/admin/app/(all)/(dashboard)/workspace/page.tsx (1)
meta(171-171)
apps/admin/vite.config.ts (1)
apps/admin/server.mjs (1)
__dirname(8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
🔇 Additional comments (30)
apps/admin/app/(all)/(dashboard)/general/intercom.tsx (3)
3-3: LGTM: Import follows codebase conventions.The removal of the
FCimport is correct. Based on learnings, React types (includingReact.FC) are globally available through the TypeScript configuration in this repository and can be used without explicitly importing the React namespace.
7-7: Good use of type-only import.Using
import typefor the interface import correctly indicates that this is only used at the type level, which can improve tree-shaking and makes the intent clearer.
16-16: LGTM: React.FC usage is correct for this codebase.The use of
React.FCwithout importing the React namespace is valid in this repository. Based on learnings, React types are globally available through TypeScript configuration when@types/reactis installed, allowing namespace usage without explicit imports.apps/admin/core/hooks/store/use-theme.tsx (1)
4-4: LGTM! Type-only import correctly applied.The conversion to
import typeis appropriate sinceIThemeStoreis only used for the return type annotation. This improves tree-shaking and clarifies the type-only usage.apps/admin/core/hooks/store/use-workspace.tsx (1)
4-4: LGTM! Consistent with the type-only import pattern.Correctly applies
import typeforIWorkspaceStore, which is only used as a return type annotation.apps/admin/core/store/instance.store.ts (1)
4-16: LGTM! Correct distinction between runtime and type-only imports.The refactor correctly:
- Converts type annotations (
TInstanceStatus, interfaces from@plane/types,CoreRootStore) toimport type- Preserves
EInstanceStatusas a runtime import since it's used at line 116 (EInstanceStatus.ERROR)This improves tree-shaking while maintaining correct runtime behavior.
apps/admin/core/hooks/store/use-instance.tsx (1)
4-4: LGTM! Type-only import correctly applied.Consistent with the pattern across other hooks—
IInstanceStoreis only used for the return type annotation.apps/admin/app/(all)/(home)/auth-helpers.tsx (3)
5-7: Good use of type-only imports.Using
import typefor type definitions correctly reduces the runtime bundle size and makes the distinction between types and values explicit.
10-13: Correct Vite asset imports.The
?urlsuffix on asset imports is the proper Vite convention for importing static assets as URL strings. This aligns well with the migration to Vite.
30-30: More explicit type usage.Changing to
React.ReactNodeis more explicit and avoids potential naming conflicts with other imported types.apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx (1)
8-8: Good practice: Type-only imports.The conversion to
import typefor type-only imports follows TypeScript best practices and helps with build optimization and tree-shaking.Also applies to: 12-13
apps/admin/core/components/instance/loading.tsx (1)
4-5: LGTM: Correct Vite asset import pattern.The asset imports correctly use the
?urlsuffix, which is the proper Vite pattern for importing asset URLs. This ensures the imports resolve to URL strings rather than processed assets, which is appropriate for thesrcattribute of theImagecomponent.apps/admin/core/components/instance/instance-not-ready.tsx (1)
7-7: LGTM! Vite asset import convention applied correctly.The
?urlsuffix is the correct Vite convention for importing static assets as URLs.apps/admin/app/(all)/(home)/layout.tsx (1)
3-9: LGTM! Migration to React Router pattern is correct.The changes properly migrate from Next.js children-based layout to React Router's
<Outlet />pattern. The function signature correctly removes the children prop, and the Outlet component will render nested route content.apps/admin/app/(all)/(dashboard)/layout.tsx (1)
41-43: LGTM! Outlet rendering pattern is correct.The migration to render nested routes via
<Outlet />is appropriate for React Router, assuming the router import issue is resolved.apps/live/package.json (1)
52-57: Confirmed @types/[email protected] is published
No action required.apps/admin/styles/globals.css (1)
1-1: LGTM! More explicit CSS import path.The change from
@plane/propel/styles/fontsto@plane/propel/styles/fonts.cssis a safe refactor that makes the CSS import more explicit. Both export paths map to the same physical file in the propel package, maintaining backward compatibility.apps/web/app/(all)/layout.tsx (1)
8-8: LGTM! Consistent CSS import path update.The change to
@plane/propel/styles/react-day-picker.cssaligns with the explicit CSS extension pattern introduced across the codebase. Both export paths map to the same stylesheet, ensuring backward compatibility.packages/propel/package.json (2)
164-167: LGTM! Backward-compatible export strategy.The dual export mappings (with and without
.cssextensions) provide a smooth migration path. Both variants map to the same physical CSS files, ensuring backward compatibility while allowing new code to use the more explicit.cssextension.
171-175: No update required for new dependencies
All caret ranges already cover the latest patch releases and no security advisories were found.apps/admin/app/(all)/(dashboard)/ai/form.tsx (1)
6-9: LGTM! Type-only imports and React.FC usage are correct.The type-only imports for configuration types and form field types are properly separated from runtime imports, and the React.FC usage is appropriate given that React types are globally available through @types/react.
Also applies to: 19-19
apps/admin/app/compat/next/navigation.ts (3)
6-23: LGTM! useRouter shim correctly maps Next.js router API to React Router.The implementation properly memoizes the router object based on the navigate function, and the method mappings (push, replace, back, forward, refresh, prefetch) are appropriate for a compatibility shim. The no-op prefetch is acceptable since React Router doesn't have built-in prefetching.
25-28: LGTM! usePathname correctly returns the current pathname.
30-33: LGTM! useSearchParams correctly wraps React Router's hook.apps/admin/app/compat/next/link.tsx (1)
6-21: LGTM! Link shim correctly maps Next.js Link API to React Router.The component properly handles Next.js-specific props (prefetch, scroll, shallow) by ignoring them, and correctly forwards href and replace to the React Router Link. The underscore prefix for ignored props follows best practices.
apps/admin/app/types/next-link.d.ts (1)
1-12: LGTM! Type declaration correctly defines the next/link module interface.The ambient module declaration properly types the Link component and its Props, providing type safety for code that imports from "next/link".
apps/admin/package.json (2)
7-19: LGTM! Module type and scripts are correctly configured for React Router + Vite.The ESM module type aligns with Vite requirements, and the updated scripts properly support the new React Router-based development, build, and preview workflows.
42-42: Verify Express 5.x compatibility and JSON body parsing
- No
express.json()orbody-parsermiddleware detected in your server setup; ensure request bodies are parsed before mounting routes.- Confirm removal of deprecated APIs (e.g.,
res.send(status, body),app.del) and compatibility of all third-party Express middleware with Express 5.x.apps/admin/core/components/authentication/gitlab-config.tsx (1)
9-9: LGTM! Type-only import is correctly applied.The change to a type-only import for TInstanceAuthenticationMethodKeys aligns with the broader pattern in this PR of separating type imports from runtime imports.
apps/admin/core/components/authentication/password-config-switch.tsx (1)
6-6: LGTM! Type-only import is correctly applied.The change to a type-only import for TInstanceAuthenticationMethodKeys is consistent with the type-import strategy used throughout this PR.
| import { InstanceImageConfigForm } from "./form"; | ||
|
|
||
| const InstanceImagePage = observer(() => { | ||
| const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stop passing the component type into observer.
observer<React.FC<Route.ComponentProps>> makes the component expect a React.FC prop and breaks type checking. Annotate the variable and call observer plainly.
-const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => {
+const InstanceImagePage: React.FC<Route.ComponentProps> = observer(() => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => { | |
| const InstanceImagePage: React.FC<Route.ComponentProps> = observer(() => { |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/image/page.tsx around line 12, the component
is being passed a generic to observer (observer<React.FC<Route.ComponentProps>>)
which causes incorrect prop expectations; instead, declare the variable with the
proper type (e.g. const InstanceImagePage: React.FC<Route.ComponentProps> = ...)
and call observer without generics (observer(InstanceImagePage)) so the
component retains correct typing and prop checking.
| @@ -1,33 +1,28 @@ | |||
| "use client"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove the Next.js "use client" directive.
The "use client" directive is specific to Next.js and is not needed in a React Router application.
Apply this diff to remove the directive:
-"use client";
-
import { useEffect } from "react";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "use client"; | |
| import { useEffect } from "react"; |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/layout.tsx around line 1, remove the Next.js
"use client" directive at the top of the file; this directive is unnecessary for
a React Router app—delete the line containing "use client" and ensure no other
code depends on client-only behavior (if any client-only hooks/components are
present, convert them to proper client components or move them to dedicated
client files).
| import { WorkspaceCreateForm } from "./form"; | ||
|
|
||
| const WorkspaceCreatePage = observer(() => ( | ||
| const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect observer generic breaks typing.
Giving observer a component type leads to props mismatches. Annotate the component itself and leave observer unparameterized.
-const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => (
+const WorkspaceCreatePage: React.FC<Route.ComponentProps> = observer(() => (📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => ( | |
| const WorkspaceCreatePage: React.FC<Route.ComponentProps> = observer(() => ( |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx around line 8, the
observer generic is incorrectly used and causes prop type mismatches; remove the
generic parameter from observer and instead annotate the component variable
itself with the correct React.FC<Route.ComponentProps> type (i.e. declare the
const with that type) so observer is called unparameterized around the
component; update the declaration accordingly so TypeScript sees the component
props on the variable rather than on observer.
| @@ -1,16 +1,12 @@ | |||
| "use client"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove obsolete Next.js directive.
The "use client" directive is specific to Next.js App Router and has no meaning in a React Router + Vite application. It should be removed as part of the migration.
Apply this diff:
-"use client";
-
import { useEffect } from "react";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "use client"; | |
| import { useEffect } from "react"; | |
| // …rest of the file unchanged |
🤖 Prompt for AI Agents
In apps/admin/app/(all)/user.provider.tsx around line 1, remove the obsolete
Next.js "use client" directive at the top of the file; this file is now part of
a React Router + Vite app so the directive is meaningless—delete that line and
ensure no other top-of-file directives remain.
| import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url"; | ||
|
|
||
| export const InstanceNotReady: FC = () => ( | ||
| export const InstanceNotReady: React.FC = () => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing React import for React.FC type.
The component uses React.FC but React is not imported. This will cause a ReferenceError at runtime.
Apply this diff to add the React import:
+"use client";
+
+import React from "react";
import Image from "next/image";
import Link from "next/link";Note: Also address the other flagged issues (remove "use client", replace Next.js imports) as indicated in previous comments.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/admin/core/components/instance/instance-not-ready.tsx around line 9, the
component uses the React.FC type but React is not imported, and previous review
flags require removing the "use client" directive and replacing deprecated
Next.js imports; add an import for React (import React from 'react') at the top
so React.FC is defined, remove any "use client" directive from the file, and
swap any Next.js-specific imports to the recommended alternatives per prior
comments (e.g., use next/navigation or framework-agnostic APIs as instructed)
ensuring all imports are valid and the component compiles without runtime
ReferenceError.
| import Image from "next/image"; | ||
| import { useTheme } from "next-themes"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Critical: Replace Next.js-specific imports for React Router migration.
This component still imports next/image and next-themes, which are Next.js-specific packages. Since this PR migrates to React Router + Vite, these imports will cause runtime failures.
Replace with:
next/image→ standard<img>tag or a custom image componentnext-themes→ a React Router/Vite-compatible theming solution (e.g.,usehooks-tstheme hook, context-based theme provider, or a library likeuse-theme)
Run the following script to check if these Next.js packages are shimmed or replaced elsewhere in the codebase:
🏁 Script executed:
#!/bin/bash
# Check for Next.js package usage and potential shims/replacements
echo "=== Checking for next/image usage ==="
rg -n --type=ts --type=tsx -C2 "from [\"']next/image[\"']"
echo -e "\n=== Checking for next-themes usage ==="
rg -n --type=ts --type=tsx -C2 "from [\"']next-themes[\"']"
echo -e "\n=== Checking for potential shims or replacements ==="
fd -e ts -e tsx -e js | xargs rg -l "next/image|next-themes" | head -20
echo -e "\n=== Checking package.json dependencies ==="
fd package.json --exec cat {} \; | jq '.dependencies, .devDependencies' 2>/dev/null | grep -E "next|theme"Length of output: 1538
Critical: Remove Next.js imports in apps/admin/core/components/instance/loading.tsx, lines 1-2. No shims or replacements detected; next and next-themes remain in dependencies and code. Use a standard <img> or custom image component and a Vite-compatible theming solution instead.
🤖 Prompt for AI Agents
In apps/admin/core/components/instance/loading.tsx lines 1-2, remove the
Next.js-specific imports (import Image from "next/image"; import { useTheme }
from "next-themes";) and replace their usage: switch to a standard <img> element
or your project’s image component and use the Vite-compatible theming solution
(or a simple context/hook) instead of useTheme; update any props or attributes
previously tied to next/image (width/height/layout) to standard HTML attributes
or your image component API, and wire theme detection to the chosen replacement
so the component no longer imports next or next-themes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
apps/admin/vite.config.ts (1)
1-4: The__dirnameissue flagged in the previous review remains unresolved.As noted in the earlier review, this file uses
__dirnameat lines 55, 59, 63, 64, and 65, but__dirnameis undefined in ES modules. The suggested fix from the previous review should be applied.For reference, the solution is already implemented correctly in
apps/admin/server.mjsat line 7:const __dirname = path.dirname(fileURLToPath(import.meta.url));Apply the same pattern here by adding the necessary imports and definition at the top of this file.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
apps/admin/package.json(2 hunks)apps/admin/server.mjs(1 hunks)apps/admin/vite.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/admin/server.mjs
🧰 Additional context used
🧬 Code graph analysis (1)
apps/admin/vite.config.ts (1)
apps/admin/server.mjs (1)
__dirname(8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/admin/vite.config.ts (1)
59-72: Configuration looks well-structured for the migration.The plugin configuration, alias mappings, and resolve options are appropriate:
- React Router and tsconfig path plugins are correctly configured
- Next.js compatibility shims support the incremental migration
preserveSymlinks: trueis properly documented for Docker/pnpm workspaces- React deduplication prevents common issues with multiple instances
| "debug": "4.4.3", | ||
| "dom4": "^2.1.6", | ||
| "dotenv": "^16.4.5", | ||
| "express": "^5.1.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align Express runtime and type declarations.
express is pinned to ^5.1.0, but we’re still pulling in @types/[email protected], which models the Express 4 API. This mismatch will surface incorrect typings (and potentially type errors) now that the runtime has moved to v5. Either stay on [email protected] or move fully to v5 by removing the outdated DefinitelyTyped package and relying on the v5-provided types (or the matching v5 typings package, if available).
Also applies to: 82-82
🤖 Prompt for AI Agents
In apps/admin/package.json around lines 45 and 82, the project depends on
express@^5.1.0 while still pulling @types/[email protected] (Express 4 types);
either pin express to a 4.x release to match the @types package or fully adopt
Express 5 by removing @types/express (and any other v4-only type packages) so
TypeScript uses Express 5's built-in types (or the correct v5 typing package if
one exists); update package.json accordingly, run reinstall to refresh lockfile,
and verify no remaining type imports reference the old DefinitelyTyped package.
| define: { | ||
| "process.env": JSON.stringify(publicEnv), | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Consider individual key replacements for process.env variables.
Defining process.env as a serialized JSON object may cause issues because it replaces the entire process.env reference with a string. Code that accesses process.env.KEY might not work as expected.
The standard Vite approach is to define individual keys:
- define: {
- "process.env": JSON.stringify(publicEnv),
- },
+ define: Object.entries(publicEnv).reduce((acc, [key, value]) => {
+ acc[`process.env.${key}`] = JSON.stringify(value);
+ return acc;
+ }, {} as Record<string, string>),Alternatively, migrate to Vite's standard import.meta.env pattern for new code while maintaining process.env compatibility only where needed.
Verify that the current serialization approach works correctly with your codebase:
🏁 Script executed:
#!/bin/bash
# Search for process.env usage patterns in the admin app
rg -nP --type=ts --type=tsx -C2 'process\.env\.' apps/admin/Length of output: 27
🏁 Script executed:
#!/bin/bash
# Search for any process.env.KEY usage in .ts/.tsx files under apps/admin
rg -nP 'process\.env\.' -g '*.ts' -g '*.tsx' -n -C2 apps/admin/Length of output: 1799
Define individual process.env keys instead of the entire object.
Replacing process.env with a serialized JSON string breaks all process.env.KEY lookups (e.g. in vite.config.ts, server/app.ts, react-router.config.ts). Use a per-key mapping:
define: Object.entries(publicEnv).reduce((acc, [key, value]) => {
acc[`process.env.${key}`] = JSON.stringify(value);
return acc;
}, {} as Record<string, string>),Alternatively, migrate to import.meta.env for new code.
🤖 Prompt for AI Agents
In apps/admin/vite.config.ts around lines 32 to 34, the define currently assigns
the entire process.env object which breaks individual process.env.KEY lookups;
change it to define each env key separately by mapping publicEnv entries into
keys of the form "process.env.KEY" with JSON.stringified values (e.g., using
Object.entries(publicEnv).reduce to build a Record<string,string>), so that Vite
replaces each process.env.KEY correctly (or alternatively migrate code to use
import.meta.env for new code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/admin/app/root.tsx (1)
59-65: Enhance ErrorBoundary to display error details for better debugging.The ErrorBoundary currently shows only a generic message without error details, making debugging difficult. Use React Router's
useRouteErrorhook to access and display error information, especially helpful during development.Apply this diff to improve the ErrorBoundary:
+import { useRouteError, isRouteErrorResponse } from "react-router"; + export function ErrorBoundary() { + const error = useRouteError(); + + let errorMessage: string; + let errorStatus: string | undefined; + + if (isRouteErrorResponse(error)) { + errorMessage = error.data?.message || error.statusText; + errorStatus = `${error.status}`; + } else if (error instanceof Error) { + errorMessage = error.message; + } else { + errorMessage = "Unknown error"; + } + return ( - <div> - <p>Something went wrong.</p> + <div className="flex min-h-screen items-center justify-center p-4"> + <div className="max-w-md space-y-4 text-center"> + {errorStatus && <h1 className="text-4xl font-bold">{errorStatus}</h1>} + <h2 className="text-2xl font-semibold">Something went wrong</h2> + <p className="text-gray-600">{errorMessage}</p> + {process.env.NODE_ENV === "development" && error instanceof Error && error.stack && ( + <pre className="mt-4 max-h-96 overflow-auto rounded bg-gray-100 p-4 text-left text-xs"> + {error.stack} + </pre> + )} + </div> </div> ); }apps/admin/app/compat/next/helper.ts (1)
30-30: Consider structured logging for production.Using
console.warnfor error cases may be noisy in production. For a compatibility shim during migration, this is acceptable, but consider using a structured logging library or conditional logging based on environment if this code persists long-term.apps/admin/app/compat/next/navigation.ts (1)
18-20: Prefetch is a no-op; expected performance impact.The
prefetchmethod is implemented as a no-op, meaning any code relying on route prefetching for performance optimization will lose that benefit. This is an acceptable trade-off for a compatibility shim, as the app remains functional—just without the prefetch performance enhancement.If prefetching is critical, consider implementing a custom solution using React Router's route preloading mechanisms or dynamic imports.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/admin/app/compat/next/helper.ts(1 hunks)apps/admin/app/compat/next/link.tsx(1 hunks)apps/admin/app/compat/next/navigation.ts(1 hunks)apps/admin/app/error.tsx(0 hunks)apps/admin/app/root.tsx(1 hunks)apps/api/plane/license/api/views/admin.py(3 hunks)
💤 Files with no reviewable changes (1)
- apps/admin/app/error.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/admin/app/compat/next/link.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
apps/admin/app/compat/next/helper.ts (1)
apps/api/plane/settings/storage.py (1)
url(16-17)
apps/admin/app/compat/next/navigation.ts (1)
apps/admin/app/compat/next/helper.ts (1)
ensureTrailingSlash(6-33)
apps/admin/app/root.tsx (1)
apps/admin/app/providers.tsx (1)
AppProviders(19-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
🔇 Additional comments (7)
apps/admin/app/root.tsx (3)
24-39: LGTM! Layout component follows React Router best practices.The HTML shell structure is correct with proper component placement (Meta/Links in head, Scripts at end of body) and appropriate use of
suppressHydrationWarningfor hydration consistency. The integration with AppProviders maintains the existing provider setup.
41-53: Verify if hardcoded og:url should be environment-specific.The og:url is hardcoded to "https://plane.so/" which works for production but might need to be configurable for different environments (staging, preview, development). Verify whether this is intentional or if the URL should be derived from environment variables or window.location.
If environment-specific URLs are needed, consider using an environment variable:
const APP_URL = process.env.APP_URL || "https://plane.so/"; export const meta: Route.MetaFunction = () => [ // ... other meta tags { property: "og:url", content: APP_URL }, // ... ];
55-57: LGTM! Root component correctly renders Outlet.The default Root component follows the expected React Router pattern by simply rendering an Outlet for nested routes.
apps/api/plane/license/api/views/admin.py (1)
232-232: Trailing slashes are consistently applied to all admin auth redirects
Bothurljoin(..., is_admin=True)calls inadmin.pynow use"general/"; no other admin redirect paths require adjustment.apps/admin/app/compat/next/navigation.ts (2)
7-24: LGTM! Solid shim implementation.The
useRouterhook correctly adapts React Router's navigation API to match Next.js conventions. The memoization with[navigate]dependency is appropriate, and the use ofensureTrailingSlashmaintains consistency with Next.js routing behavior.
15-17: No usages ofrefresh()found—ignore this shim.No call sites reference this
refresh()implementation in the admin app; the hard-vs-soft refresh distinction has no impact.Likely an incorrect or invalid review comment.
apps/admin/app/compat/next/helper.ts (1)
6-33: Verify query-only and hash-only URL handling.
ensureTrailingSlash("?foo=bar")andensureTrailingSlash("#section")both return full dummy URLs (http://dummy.com?foo=bar,http://dummy.com#section) instead of"/?foo=bar"or"/#section". Confirm whether your routing ever uses query-only or hash-only URLs; if so, add explicit checks to preserve the leading/.
| base_host(request=request, is_admin=True, ), | ||
|
|
||
| "?" + urlencode(exc.get_error_dict()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the inconsistent formatting.
The trailing comma and empty line create unnecessary clutter and are inconsistent with other similar urljoin calls in the file (e.g., lines 98-100, 160-162).
Apply this diff to clean up the formatting:
- url = urljoin(
- base_host(request=request, is_admin=True, ),
-
- "?" + urlencode(exc.get_error_dict()),
- )
+ url = urljoin(
+ base_host(request=request, is_admin=True),
+ "?" + urlencode(exc.get_error_dict()),
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| base_host(request=request, is_admin=True, ), | |
| "?" + urlencode(exc.get_error_dict()), | |
| url = urljoin( | |
| base_host(request=request, is_admin=True), | |
| "?" + urlencode(exc.get_error_dict()), | |
| ) |
🤖 Prompt for AI Agents
In apps/api/plane/license/api/views/admin.py around lines 137 to 139, clean up
inconsistent formatting in the urljoin call: remove the trailing comma after
base_host(...) and the extra blank line so the arguments match the style used
elsewhere (e.g., lines 98-100, 160-162); ensure the third argument remains on
the same logical block as the preceding call and there are no stray commas or
blank lines between arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
apps/admin/vite.config.ts (2)
1-4: Define__dirnamein ESM configs.
__dirnameis referenced below but never defined in this ESM file (Node sets"type":"module"). This will throw at runtime; importfileURLToPathfromnode:urland derive__dirnamebefore use.
32-34: Avoid overwritingprocess.env.Replacing the entire
process.envobject with a JSON string breaks everyprocess.env.KEYlookup across the admin app. Map each key individually (or migrate toimport.meta.env) so property accesses still work.apps/admin/nginx/nginx.conf (1)
12-14: Duplicate: Real IP configuration issues already flagged.Past review comments have already identified critical security issues here: trusting all client IPs (
set_real_ip_from 0.0.0.0/0;) allows IP spoofing, and the header name has a typo (X-Forward-Forshould beX-Forwarded-For). These issues must be addressed as noted in previous feedback.
🧹 Nitpick comments (2)
apps/admin/nginx/nginx.conf (2)
1-1: Consider usingautofor worker processes.The worker processes are hardcoded to
4. Usingautoallows nginx to detect the number of available CPU cores, improving portability across different deployment environments.-worker_processes 4; +worker_processes auto;
7-11: Enable gzip compression for better performance.Gzip compression is not configured. Enabling it would reduce bandwidth usage and improve load times for static assets (HTML, CSS, JS).
Add gzip configuration in the
httpblock:http { include mime.types; default_type application/octet-stream; + + # Enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/admin/nginx/nginx.conf(1 hunks)apps/admin/vite.config.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/admin/vite.config.ts (1)
apps/admin/server.mjs (1)
__dirname(8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
| set_real_ip_from 0.0.0.0/0; | ||
| real_ip_recursive on; | ||
| real_ip_header X-Forward-For; | ||
| limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apply the rate limit zone or remove the unused declaration.
The rate limiting zone mylimit is defined but never applied with a limit_req directive in any location block. This declaration allocates 10MB of memory without providing any protection against DoS or brute force attacks.
Either apply the zone in your location block:
location / {
root /usr/share/nginx/html;
index index.html index.htm;
+ limit_req zone=mylimit burst=20 nodelay;
try_files $uri $uri/ /index.html;
}Or remove the unused zone definition if rate limiting is not needed:
- limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; | |
| location / { | |
| root /usr/share/nginx/html; | |
| index index.html index.htm; | |
| limit_req zone=mylimit burst=20 nodelay; | |
| try_files $uri $uri/ /index.html; | |
| } |
🤖 Prompt for AI Agents
In apps/admin/nginx/nginx.conf around line 15, the rate limit zone "mylimit" is
declared but never applied; either apply it with a limit_req directive in the
appropriate location/server blocks (e.g., add limit_req zone=mylimit burst=...
nodelay or similar to the location(s) you want protected) or remove the
limit_req_zone declaration to avoid allocating the 10m zone when unused.
| server { | ||
| listen 3000; | ||
|
|
||
| location / { | ||
| root /usr/share/nginx/html; | ||
| index index.html index.htm; | ||
| try_files $uri $uri/ /index.html; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add security headers for defense-in-depth.
The server block lacks security headers that protect against XSS, clickjacking, MIME-sniffing, and other attacks. For an admin application, these headers are critical for defense-in-depth.
Add security headers inside the location / block:
location / {
root /usr/share/nginx/html;
index index.html index.htm;
+ # Security headers
+ add_header X-Frame-Options "DENY" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
try_files $uri $uri/ /index.html;
}Note: Adjust CSP directives based on your actual resource requirements.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| server { | |
| listen 3000; | |
| location / { | |
| root /usr/share/nginx/html; | |
| index index.html index.htm; | |
| try_files $uri $uri/ /index.html; | |
| } | |
| } | |
| server { | |
| listen 3000; | |
| location / { | |
| root /usr/share/nginx/html; | |
| index index.html index.htm; | |
| # Security headers | |
| add_header X-Frame-Options "DENY" always; | |
| add_header X-Content-Type-Options "nosniff" always; | |
| add_header X-XSS-Protection "1; mode=block" always; | |
| add_header Referrer-Policy "strict-origin-when-cross-origin" always; | |
| add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always; | |
| try_files $uri $uri/ /index.html; | |
| } | |
| } |
🤖 Prompt for AI Agents
In apps/admin/nginx/nginx.conf around lines 20 to 28, the server block currently
serves the admin UI but lacks security response headers; add header directives
inside the location / block to set at minimum: X-Frame-Options: DENY (prevent
clickjacking), X-Content-Type-Options: nosniff (prevent MIME sniffing),
X-XSS-Protection: 1; mode=block or a modern equivalent, Referrer-Policy:
no-referrer-when-downgrade (or stricter), Strict-Transport-Security with an
appropriate max-age and includeSubDomains if TLS is used, and
Content-Security-Policy with a restrictive default-src and explicit
script/style/resource sources (use a placeholder CSP that you will tune to match
the app’s actual resources); add these header lines using the nginx "add_header"
directive and ensure they apply to responses (use always if needed) and that CSP
is tailored to the app before deploying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/admin/package.json (1)
41-41: Remove the Express 4 typings when adopting Express 5.
expressis now pinned to^5.1.0, but the dev dependency still pulls in@types/[email protected]. Those legacy types model the Express 4 API and will clash with the runtime’s built-in v5 declarations, producing incorrect typings (and potential build breaks) as soon as you touch v5 APIs. Either downgrade back to Express 4 or, preferably, drop the DefinitelyTyped package so TypeScript consumes the v5-provided types (or replace it with the matching v5 typings package if you need one). Please updatepackage.jsonaccordingly and regenerate the lockfile."express": "^5.1.0", ... - "@types/express": "4.17.23",Also applies to: 71-71
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/admin/package.json(2 hunks)packages/propel/package.json(1 hunks)packages/services/package.json(2 hunks)packages/ui/package.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/propel/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: Analyze (javascript)
| "main": "./dist/index.cjs", | ||
| "module": "./dist/index.js", | ||
| "types": "./dist/index.d.cts" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the types entry to match the emitted declarations.
tsdown still emits dist/index.d.ts; switching the manifest to dist/index.d.cts makes TypeScript consumers fail resolution immediately. Please keep the types field pointed at the actual .d.ts output (or update the build to emit .d.cts before flipping this).
Apply this diff:
- "types": "./dist/index.d.cts"
+ "types": "./dist/index.d.ts"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "main": "./dist/index.cjs", | |
| "module": "./dist/index.js", | |
| "types": "./dist/index.d.cts" | |
| "main": "./dist/index.cjs", | |
| "module": "./dist/index.js", | |
| "types": "./dist/index.d.ts" |
🤖 Prompt for AI Agents
In packages/services/package.json around lines 43 to 45, the "types" field
incorrectly points to "./dist/index.d.cts" while the build emits
"./dist/index.d.ts"; change the "types" entry to "./dist/index.d.ts" (or
alternatively update the build to emit .d.cts before changing the manifest) so
TypeScript consumers can resolve declarations correctly.
1727e32 to
5e336af
Compare
5e336af to
0eda662
Compare
|
Linked to Plane Work Item(s) References This comment was auto-generated by Plane |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (16)
apps/api/plane/license/api/views/admin.py (1)
137-138: Fix the formatting inconsistency (already flagged).The trailing comma after
is_admin=True,and the empty line between function arguments have already been identified by previous reviewers as creating unnecessary clutter and inconsistent formatting.Apply the fix suggested in previous reviews:
- url = urljoin( - base_host(request=request, is_admin=True, ), - - "?" + urlencode(exc.get_error_dict()), - ) + url = urljoin( + base_host(request=request, is_admin=True), + "?" + urlencode(exc.get_error_dict()), + )apps/admin/core/components/instance/instance-not-ready.tsx (1)
1-9: Clarify Next.js compatibility layer approach.Previous reviews flagged the
"use client"directive (line 1) and Next.js imports fromnext/imageandnext/link(lines 3-4) as critical issues requiring removal for React Router compatibility. These comments were marked as "✅ Addressed in commits 1a9ebc8 to 1727e32", yet the current code retains all these Next.js-specific patterns.If compatibility shims now handle these imports (per the PR description mentioning "compatibility layers for next/link and next/image utilities"), please confirm the shim implementations are working correctly. Otherwise, these represent regressions from previously addressed issues.
Run the following to verify if compatibility shims exist and are properly configured:
#!/bin/bash # Description: Verify Next.js compatibility shims and their configuration # Check for next/link and next/image module definitions echo "Searching for Next.js compatibility shim files..." fd --type f "(link|image)" apps/admin/core/lib apps/admin/core/components --exec rg -l "next/(link|image)" # Look for Vite alias or resolve configuration echo -e "\nChecking Vite configuration for module aliases..." cat apps/admin/vite.config.ts 2>/dev/null || cat apps/admin/vite.config.js 2>/dev/null # Search for actual shim implementations echo -e "\nSearching for shim implementation exports..." ast-grep --pattern 'export default function Image($$$) { $$$ }' ast-grep --pattern 'export default function Link($$$) { $$$ }'apps/admin/core/components/instance/failure.tsx (1)
1-1: Remove Next.js-specific "use client" directive.The
"use client"directive is specific to Next.js and serves no purpose in a React Router + Vite environment. It should be removed as part of the migration.Apply this diff to remove the directive:
-"use client"; import { observer } from "mobx-react";apps/admin/ce/components/authentication/authentication-modes.tsx (1)
101-101: Revert to explicit React.FC annotation to match codebase patterns.As noted in a previous review, the observer typing pattern should use explicit React.FC annotation rather than a generic type parameter to maintain consistency across the codebase.
Apply this diff to revert to the prevailing pattern:
-export const AuthenticationModes = observer<React.FC<TAuthenticationModeProps>>((props) => { +export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer((props) => {apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx (1)
8-8: Incorrectobservergeneric breaks typing.The generic type parameter on
observerdoesn't match the arrow function pattern. Type the component variable itself instead.Apply this diff:
-const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => ( +const WorkspaceCreatePage: React.FC<Route.ComponentProps> = observer(() => (apps/admin/app/(all)/(dashboard)/ai/page.tsx (1)
12-12: Incorrectobservergeneric breaks typing.The generic type parameter on
observerdoesn't match the arrow function pattern. Type the component variable itself instead.Apply this diff:
-const InstanceAIPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceAIPage: React.FC<Route.ComponentProps> = observer(() => {apps/admin/Dockerfile.admin (2)
76-79: Critical: Environment variables needed for nginx template substitution.The production stage starts fresh from
nginx:1.27-alpineand doesn't inherit the ENV declarations from the installer stage (lines 31-59). If the nginx configuration template at/etc/nginx/nginx.conf(line 78) uses variable substitution forNEXT_PUBLIC_*values, these must be declared in the production stage.Add the required environment variables in the production stage:
FROM nginx:1.27-alpine AS production + +ARG NEXT_PUBLIC_API_BASE_URL="" +ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL +ARG NEXT_PUBLIC_API_BASE_PATH="/api" +ENV NEXT_PUBLIC_API_BASE_PATH=$NEXT_PUBLIC_API_BASE_PATH +ARG NEXT_PUBLIC_ADMIN_BASE_URL="" +ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL +ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode" +ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH COPY apps/admin/nginx/nginx.conf /etc/nginx/nginx.conf
83-84: Install curl before using it in healthcheck.The
nginx:1.27-alpinebase image doesn't include curl, causing the healthcheck to fail with exit 127.Add curl to the production stage:
FROM nginx:1.27-alpine AS production + +RUN apk add --no-cache curl COPY apps/admin/nginx/nginx.conf /etc/nginx/nginx.confapps/admin/vite.config.ts (2)
1-4: Define__dirnamefor ESM to avoid runtime ReferenceError.
__dirnameis used later but not defined under"type": "module". Add the ESM equivalent.import path from "node:path"; +import { fileURLToPath } from "node:url"; import { reactRouter } from "@react-router/dev/vite"; import { defineConfig } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; +const __dirname = path.dirname(fileURLToPath(import.meta.url));
31-34: Don’t replace the entireprocess.env; map individual keys (or migrate toimport.meta.env).Replacing
process.envwholesale is brittle and can break code expecting an object. Define per‑key replacements instead.- define: { - "process.env": JSON.stringify(publicEnv), - }, + define: Object.entries(publicEnv).reduce((acc, [key, value]) => { + acc[`process.env.${key}`] = JSON.stringify(value); + return acc; + }, {} as Record<string, string>),Optional (recommended): adopt Vite’s envs for new code and keep the above only for legacy:
- Add
envPrefix: ["NEXT_PUBLIC_"]at the top level.- Use
import.meta.env.NEXT_PUBLIC_*going forward.packages/services/package.json (1)
45-45: Resolve the types field mismatch (flagged previously).This was raised in the prior review:
tsdownemitsdist/index.d.ts, but the manifest points todist/index.d.cts. This breaks TypeScript consumer resolution. Either keeptypespointed at the actual emitted file (./dist/index.d.ts) or update the build to emit.d.ctsbefore flipping this field.Apply this diff to match the actual build output:
- "types": "./dist/index.d.cts" + "types": "./dist/index.d.ts"apps/admin/app/(all)/(dashboard)/image/page.tsx (1)
12-12: Stop passing the component type intoobserver.
observer<React.FC<Route.ComponentProps>>makes the component expect aReact.FCprop and breaks type checking. Annotate the variable and callobserverplainly.Apply this diff:
-const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceImagePage: React.FC<Route.ComponentProps> = observer(() => {apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx (1)
19-19: Stop passing the component type intoobserver.
observer<React.FC<Route.ComponentProps>>makes the component expect aReact.FCprop and breaks type checking. Annotate the variable and callobserverplainly.Apply this diff:
-const InstanceGitlabAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => { +const InstanceGitlabAuthenticationPage: React.FC<Route.ComponentProps> = observer(() => {apps/admin/server/app.ts (1)
27-36: Previous review comment remains unaddressed.The
secure: falsesetting on line 32 disables TLS certificate validation, creating a MITM vulnerability in production. This issue was flagged in a previous review but has not been resolved.apps/admin/server.mjs (1)
26-26: Critical: PORT derivation already flagged by previous reviewersThis line is a duplicate of issues already raised. When
NEXT_PUBLIC_ADMIN_BASE_URLomits an explicit port,parsedBaseUrl.portbecomes an empty string, causingNumber.parseIntto returnNaNand server startup to fail.Refer to the existing review comments above for the recommended fix that checks
process.env.PORTbefore falling back to the parsed URL port and then to a default value.apps/admin/package.json (1)
41-41: Express v5 runtime with v4 type definitions—mismatch still unresolved.Line 41 pins
express@^5.1.0, but line 71 still references@types/[email protected](Express 4 types). This mismatch will cause incorrect typings and type errors. Align the version pair: either downgrade to[email protected](with matching types) or fully adopt v5 by removing@types/expressand relying on Express v5's built-in or native type exports.Verify the correct v5 typing story (run
npm info [email protected]to check if v5 ships types internally or if there's a separate@types/[email protected]package), then update accordingly.Also applies to: 71-71
🧹 Nitpick comments (12)
.gitignore (1)
106-107: Remove duplicatebuild/pattern.Line 106 duplicates the
build/pattern already present at line 21, reducing clarity. Line 107 correctly adds.react-router/for the new React Router workspace.Apply this diff to remove the duplicate:
-build/ -.react-router/ +.react-router/packages/propel/tsdown.config.ts (1)
42-45: Consider adding inline documentation for the dual CSS export pattern.The explicit
.cssextension exports (lines 42, 44) alongside the extension-less variants (lines 43, 45) serve a valid purpose for Vite compatibility, as noted in the past review. However, to aid future maintainers, consider adding a brief inline comment explaining why both variants are necessary.Apply this diff to add clarifying documentation:
customExports: (exports) => ({ ...exports, + // Explicit .css extensions required for Vite import compatibility "./styles/fonts.css": "./dist/styles/fonts/index.css", "./styles/fonts": "./dist/styles/fonts/index.css", "./styles/react-day-picker.css": "./dist/styles/react-day-picker.css", "./styles/react-day-picker": "./dist/styles/react-day-picker.css", }),packages/ui/package.json (1)
36-107: Consider documenting dependency rationale for React Router migration context.With ~70+ new dependencies added as part of the React Router migration, it would be beneficial to add a comment or accompanying documentation explaining:
- Which dependencies are essential for the React Router + Vite architecture
- Which are UI framework upgrades (Radix UI expansion)
- Which support code generation or utility functions
- Any breaking changes from the Next.js architecture that required these additions
This helps future maintainers understand the migration's impact on the dependency tree.
apps/admin/app/(all)/(home)/page.tsx (1)
1-1: Remove obsolete "use client" directive.This directive is a Next.js React Server Components feature that has no effect in React Router. Consider removing it as part of the migration cleanup.
-"use client"; - import { observer } from "mobx-react";apps/admin/vite.config.ts (1)
6-25: Const‑type the public env key list for stronger types and safer refactors.Minor improvement: const‑typing the keys enables better inference and avoids accidental typos.
-const PUBLIC_ENV_KEYS = [ +const PUBLIC_ENV_KEYS = [ "NEXT_PUBLIC_API_BASE_URL", "NEXT_PUBLIC_API_BASE_PATH", "NEXT_PUBLIC_ADMIN_BASE_URL", "NEXT_PUBLIC_ADMIN_BASE_PATH", "NEXT_PUBLIC_SPACE_BASE_URL", "NEXT_PUBLIC_SPACE_BASE_PATH", "NEXT_PUBLIC_LIVE_BASE_URL", "NEXT_PUBLIC_LIVE_BASE_PATH", "NEXT_PUBLIC_WEB_BASE_URL", "NEXT_PUBLIC_WEB_BASE_PATH", "NEXT_PUBLIC_WEBSITE_URL", "NEXT_PUBLIC_SUPPORT_EMAIL", -]; +] as const; -const publicEnv = PUBLIC_ENV_KEYS.reduce<Record<string, string>>((acc, key) => { +const publicEnv = (PUBLIC_ENV_KEYS as readonly string[]).reduce<Record<string, string>>((acc, key) => { acc[key] = process.env[key] ?? ""; return acc; }, {});apps/admin/app/components/404.tsx (1)
9-9: Remove unnecessary template literal backticks.The backticks serve no purpose here since there's no string interpolation.
Apply this diff:
- <div className={`h-screen w-full overflow-hidden bg-custom-background-100`}> + <div className="h-screen w-full overflow-hidden bg-custom-background-100">apps/admin/server/app.ts (3)
1-1: Remove unnecessary side-effect import.The side-effect import of
"react-router"on line 1 appears unnecessary—React Router SSR does not require it, and you're already importing the needed exports from@react-router/expresson line 2.Apply this diff to remove the side-effect import:
-import "react-router"; import { createRequestHandler } from "@react-router/express";
7-9: Consider validating the API base URL.The environment variable
NEXT_PUBLIC_API_BASE_URLis normalized but not validated. A malformed URL could cause the proxy to fail at runtime. Consider validating the URL format during startup (e.g., withnew URL()) and exiting with a clear error message if invalid.Example validation:
const NEXT_PUBLIC_API_BASE_URL = (() => { const raw = process.env.NEXT_PUBLIC_API_BASE_URL || "http://127.0.0.1:8000"; const normalized = raw.replace(/\/$/, ""); try { new URL(normalized); // Validate URL format return normalized; } catch (err) { throw new Error(`Invalid NEXT_PUBLIC_API_BASE_URL: ${raw}`); } })();
40-44: Add error handling for the SSR request handler.If the virtual module import fails or the React Router request handler throws an error, the server will crash. Consider wrapping the request handler with error handling middleware to gracefully handle SSR failures and return appropriate error responses to clients.
Example error boundary:
router.use( createRequestHandler({ build: () => import("virtual:react-router/server-build"), }) ); // Add error handler after the SSR handler router.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { console.error("SSR error:", err); res.status(500).send("Internal Server Error"); });apps/admin/server.mjs (1)
58-65: Consider absolute paths for build artifactsThe production mode static asset serving and app import use relative paths (
"build/client/assets","build/client", andBUILD_PATH = "./build/server/index.js"). If the Node process is started from a directory other thanapps/admin, these imports will fail.Since line 10 already uses
__dirnamefor the.envpath, consider making BUILD_PATH and static asset paths absolute for consistency and reliability.-const BUILD_PATH = "./build/server/index.js"; +const BUILD_PATH = path.resolve(__dirname, "build/server/index.js");And in production mode:
- app.use("/assets", express.static("build/client/assets", { immutable: true, maxAge: "1y" })); + app.use("/assets", express.static(path.resolve(__dirname, "build/client/assets"), { immutable: true, maxAge: "1y" })); app.use(morgan("tiny")); - app.use(express.static("build/client", { maxAge: "1h" })); + app.use(express.static(path.resolve(__dirname, "build/client"), { maxAge: "1h" }));apps/admin/package.json (2)
33-33: Clarify the need for@vercel/[email protected].Since the migration is shifting away from Vercel's infrastructure (Next.js) to a static Vite + nginx model, this package seems incongruous. Is it still actively used, or is it legacy from the Next.js era? If unused, remove it to reduce the dependency surface.
29-31: Verify React Router and Vite version constraints.The React Router (
^7.9.1,^7.9.3) and Vite (7.1.7) versions are recent but pinned or loosely constrained. Ensure they are tested and stable for your monorepo's build pipeline; monitor for any peer dependency conflicts with React (catalog:), TypeScript, and other core packages. Also verify that@react-router/dev@^7.9.1(line 69) matches the runtime versions (@react-router/expressand@react-router/nodeat lines 29–30).Also applies to: 56-57, 69-69, 78-79
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (22)
apps/admin/app/assets/favicon/apple-touch-icon.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-16x16.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon-32x32.pngis excluded by!**/*.pngapps/admin/app/assets/favicon/favicon.icois excluded by!**/*.icoapps/admin/app/assets/images/404.svgis excluded by!**/*.svgapps/admin/app/assets/images/logo-spinner-dark.gifis excluded by!**/*.gifapps/admin/app/assets/images/logo-spinner-light.gifis excluded by!**/*.gifapps/admin/app/assets/images/plane-takeoff.pngis excluded by!**/*.pngapps/admin/app/assets/instance/instance-failure-dark.svgis excluded by!**/*.svgapps/admin/app/assets/instance/instance-failure.svgis excluded by!**/*.svgapps/admin/app/assets/logos/github-black.pngis excluded by!**/*.pngapps/admin/app/assets/logos/github-white.pngis excluded by!**/*.pngapps/admin/app/assets/logos/gitlab-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/google-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/oidc-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/saml-logo.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-dark.svgis excluded by!**/*.svgapps/admin/app/assets/logos/takeoff-icon-light.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern-dark.svgis excluded by!**/*.svgapps/admin/public/auth/background-pattern.svgis excluded by!**/*.svgapps/admin/public/instance/plane-takeoff.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (78)
.dockerignore(1 hunks).gitignore(1 hunks)apps/admin/.dockerignore(1 hunks)apps/admin/.eslintrc.cjs(2 hunks)apps/admin/Dockerfile.admin(1 hunks)apps/admin/app/(all)/(dashboard)/ai/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/ai/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/gitlab/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/authentication/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/authentication/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/email/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/email/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/general/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/general/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/image/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/image/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/layout.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx(2 hunks)apps/admin/app/(all)/(dashboard)/workspace/layout.tsx(0 hunks)apps/admin/app/(all)/(dashboard)/workspace/page.tsx(2 hunks)apps/admin/app/(all)/(home)/auth-helpers.tsx(2 hunks)apps/admin/app/(all)/(home)/layout.tsx(1 hunks)apps/admin/app/(all)/(home)/page.tsx(2 hunks)apps/admin/app/compat/next/helper.ts(1 hunks)apps/admin/app/compat/next/image.tsx(1 hunks)apps/admin/app/compat/next/link.tsx(1 hunks)apps/admin/app/compat/next/navigation.ts(1 hunks)apps/admin/app/components/404.tsx(1 hunks)apps/admin/app/error.tsx(0 hunks)apps/admin/app/layout.tsx(0 hunks)apps/admin/app/providers.tsx(1 hunks)apps/admin/app/root.tsx(1 hunks)apps/admin/app/routes.ts(1 hunks)apps/admin/app/types/next-image.d.ts(1 hunks)apps/admin/app/types/next-link.d.ts(1 hunks)apps/admin/app/types/next-navigation.d.ts(1 hunks)apps/admin/app/types/react-router-virtual.d.ts(1 hunks)apps/admin/ce/components/authentication/authentication-modes.tsx(2 hunks)apps/admin/core/components/common/logo-spinner.tsx(1 hunks)apps/admin/core/components/instance/failure.tsx(1 hunks)apps/admin/core/components/instance/instance-not-ready.tsx(1 hunks)apps/admin/core/components/instance/loading.tsx(1 hunks)apps/admin/core/components/new-user-popup.tsx(1 hunks)apps/admin/middleware.js(1 hunks)apps/admin/next-env.d.ts(0 hunks)apps/admin/next.config.js(0 hunks)apps/admin/nginx/nginx.conf(1 hunks)apps/admin/package.json(2 hunks)apps/admin/public/.well-known/appspecific/com.chrome.devtools.json(1 hunks)apps/admin/public/favicon/site.webmanifest(0 hunks)apps/admin/react-router.config.ts(1 hunks)apps/admin/server.mjs(1 hunks)apps/admin/server/app.ts(1 hunks)apps/admin/styles/globals.css(1 hunks)apps/admin/tsconfig.json(1 hunks)apps/admin/vite.config.ts(1 hunks)apps/api/plane/license/api/views/admin.py(3 hunks)apps/live/package.json(1 hunks)apps/web/package.json(0 hunks)packages/decorators/package.json(1 hunks)packages/editor/package.json(1 hunks)packages/editor/tsdown.config.ts(1 hunks)packages/eslint-config/package.json(1 hunks)packages/i18n/package.json(1 hunks)packages/logger/package.json(1 hunks)packages/propel/package.json(1 hunks)packages/propel/tsdown.config.ts(1 hunks)packages/services/package.json(2 hunks)packages/shared-state/package.json(1 hunks)packages/ui/package.json(1 hunks)packages/utils/package.json(1 hunks)packages/utils/src/string.ts(1 hunks)pnpm-workspace.yaml(1 hunks)
💤 Files with no reviewable changes (15)
- apps/admin/app/(all)/(dashboard)/authentication/github/layout.tsx
- apps/admin/app/error.tsx
- apps/admin/app/(all)/(dashboard)/authentication/google/layout.tsx
- apps/admin/app/(all)/(dashboard)/authentication/layout.tsx
- apps/admin/app/(all)/(dashboard)/general/layout.tsx
- apps/admin/next.config.js
- apps/admin/app/(all)/(dashboard)/authentication/gitlab/layout.tsx
- apps/admin/app/layout.tsx
- apps/admin/public/favicon/site.webmanifest
- apps/admin/app/(all)/(dashboard)/ai/layout.tsx
- apps/admin/next-env.d.ts
- apps/admin/app/(all)/(dashboard)/email/layout.tsx
- apps/web/package.json
- apps/admin/app/(all)/(dashboard)/image/layout.tsx
- apps/admin/app/(all)/(dashboard)/workspace/layout.tsx
✅ Files skipped from review due to trivial changes (6)
- .dockerignore
- apps/admin/public/.well-known/appspecific/com.chrome.devtools.json
- packages/utils/src/string.ts
- packages/shared-state/package.json
- apps/admin/styles/globals.css
- packages/i18n/package.json
🚧 Files skipped from review as they are similar to previous changes (27)
- apps/admin/.dockerignore
- apps/admin/app/(all)/(home)/layout.tsx
- apps/admin/app/providers.tsx
- apps/admin/app/(all)/(dashboard)/authentication/github/page.tsx
- apps/admin/app/compat/next/image.tsx
- apps/admin/app/(all)/(dashboard)/workspace/page.tsx
- packages/editor/package.json
- apps/admin/app/(all)/(home)/auth-helpers.tsx
- apps/admin/app/types/react-router-virtual.d.ts
- apps/admin/app/(all)/(dashboard)/authentication/google/page.tsx
- apps/admin/middleware.js
- apps/admin/.eslintrc.cjs
- apps/admin/core/components/common/logo-spinner.tsx
- apps/admin/app/types/next-image.d.ts
- apps/admin/app/types/next-navigation.d.ts
- packages/logger/package.json
- packages/decorators/package.json
- apps/admin/app/(all)/(dashboard)/general/page.tsx
- apps/admin/app/(all)/(dashboard)/authentication/page.tsx
- apps/admin/nginx/nginx.conf
- packages/propel/package.json
- apps/admin/app/root.tsx
- apps/admin/app/(all)/(dashboard)/email/page.tsx
- apps/admin/app/compat/next/link.tsx
- apps/admin/core/components/new-user-popup.tsx
- apps/admin/app/(all)/(dashboard)/layout.tsx
- packages/editor/tsdown.config.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-09T20:42:31.843Z
Learnt from: lifeiscontent
PR: makeplane/plane#7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.843Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.
Applied to files:
apps/admin/core/components/instance/failure.tsx
🔇 Additional comments (40)
packages/utils/package.json (1)
30-30: Verify dompurify compatibility across all usage contexts in this utils package.The dependency change from
isomorphic-dompurify(browser + Node.js compatible) todompurify(browser-focused) represents a significant shift.dompurifyrequires DOM APIs and will not work in Node.js server contexts without polyfills. Given that this is a utilities package, verify:
- Whether
packages/utilsis now exclusively used in client-side/browser contexts (consistent with the Vite/React Router migration)- That no code in
packages/utils/src/string.tsor elsewhere is invoked on the server side after this migration- That version 3.2.7 is a stable release without known vulnerabilities
apps/live/package.json (1)
56-56: Verify intentionality of version constraint change outside PR scope.The caret operator is removed from
@types/express, pinning the version to an exact release rather than allowing compatible semver updates. While pinning type package versions is a sound practice for reproducibility, this change appears orthogonal to the PR objectives (admin React Router migration).Confirm this is intentional and part of a broader dependency pinning effort across the monorepo, rather than accidental scope creep.
packages/eslint-config/package.json (1)
16-20: Validate major version bumps in ESLint packages.Several ESLint-related dependencies have been bumped to new major versions, which may introduce breaking changes:
eslint-config-prettier: 9.x → 10.xeslint-config-turbo: 1.x → 2.xeslint-plugin-react-hooks: 5.x → 6.xThese upgrades align with the React Router migration, but verify that lint rules and configurations remain compatible and that linting passes without new errors or deprecation warnings.
Run linting with the updated ESLint tooling across the codebase to ensure no new errors or warnings are introduced:
packages/propel/tsdown.config.ts (1)
40-41: LGTM! Parameter rename improves clarity.The parameter rename from
outtoexportsbetter reflects the purpose of the parameter and improves code readability.packages/ui/package.json (3)
39-39: Verify necessity of utility library proliferation and consider consolidation.The dependencies include many single-purpose utility libraries (camel-case, capital-case, constant-case, no-case, param-case, pascal-case, path-case, sentence-case, snake-case, upper-case, upper-case-first) that provide case conversion functionality. This represents significant potential for:
- Duplication:
lodash(line 79) already provides string casing utilities and covers many of these use cases.- Bundle bloat: These single-purpose packages add overhead that might be consolidatable.
- Maintenance complexity: Multiple overlapping dependencies increase maintenance burden.
If these case conversion utilities are truly necessary, consider documenting why
lodashutilities are insufficient. Otherwise, refactor to use lodash's built-in case functions.Also applies to: 64-106
49-49: Verify @plane/propel dependency is necessary.The new workspace dependency
@plane/propelis added but no context is provided about its purpose or whether it's actually imported in the UI package. This could be:
- A build-time-only dependency (should be in devDependencies)
- An accidental/duplicate addition
- A legitimate runtime dependency that needs clarification
Ensure this dependency is actively used in the UI package code.
53-63: Verify Radix UI sub-package coverage is complete and intentional.Multiple granular Radix UI sub-packages are added individually (react-compose-refs, react-context, react-direction, react-presence, react-primitive, react-slot, react-use-callback-ref, react-use-layout-effect). These are typically auto-installed as dependencies of higher-level Radix UI components but adding them explicitly suggests either:
- Direct usage: The code directly imports from these low-level packages (check for this)
- Version pinning: Explicit versions are needed to avoid conflicts (verify if versions differ from transitive dependencies)
- Redundancy: They're included unnecessarily (should be removed)
Consider documenting why these low-level Radix primitives are pinned explicitly in this package rather than relying on Radix UI component dependencies.
apps/api/plane/license/api/views/admin.py (1)
232-232: Verify consistent trailing slash usage across all admin redirects.The addition of trailing slashes to the "general/" redirect paths aligns with the React Router migration and URL normalization goals of this PR.
Please verify that all admin redirect URLs in this file consistently use trailing slashes. Run the following script to check for other
urljoincalls with admin redirects:Also applies to: 351-351
apps/admin/app/(all)/(home)/page.tsx (1)
11-11: LGTM! Meta function follows React Router pattern.The type import and meta function export correctly implement React Router's route metadata pattern. The title and description appropriately describe the admin home page functionality.
Also applies to: 43-46
apps/admin/core/components/instance/loading.tsx (2)
4-5: LGTM! Correct Vite asset import syntax.The
?urlsuffix correctly instructs Vite to return the asset URL as a string, which is the proper syntax for this migration.
1-2: Verify Next.js compatibility shims exist for this migration.While the PR objectives mention compatibility layers for
next/image, the past review found no shims and bothnextandnext-themespackages remaining in dependencies. The import on line 2 fornext-themesis not mentioned in the compatibility layer list.Run the following script to verify whether compatibility shims have been added:
apps/admin/core/components/instance/failure.tsx (3)
8-9: Asset imports correctly updated for Vite.The asset imports using the
?urlsuffix are the correct pattern for Vite, loading SVG files as URL strings. This properly replaces the previous Next.js public path approach.
11-11: Component typing is correct.The use of
React.FCwithout an explicit React import is correct for this codebase, as React types are globally available through TypeScript configuration.Based on learnings.
3-4: Verify Next.js compatibility shims are in place.The component continues to import from
next/imageandnext-themes. Per the PR description, compatibility layers were introduced for Next.js utilities. Ensure these shims exist and properly handle these imports in the React Router + Vite environment.Run the following script to verify the compatibility shims:
apps/admin/core/components/instance/instance-not-ready.tsx (1)
7-7: Asset import correctly updated for Vite.The asset import using
?urlcorrectly follows the Vite pattern for loading images as URL strings.apps/admin/tsconfig.json (3)
2-6: Configuration update aligns with React Router + Vite migration.The tsconfig changes appropriately reflect the architectural shift:
- Extending
react-router.jsoninstead of Next.js config- Adding
rootDirsto include React Router type generation directory (.react-router/types)- Adding
vite/clienttypes for Vite-based developmentThese changes look correct for the target architecture.
15-15: Verify the updated include pattern doesn't unintentionally exclude needed files.The include now explicitly lists
["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"]instead of relying on default includes. Confirm that:
- All necessary source directories under
apps/admin/(likeapp/,core/,ce/) are captured by the**/*glob- The
.react-router/types/**/*directory will exist and contain expected generated types- No source files are unintentionally excluded
1-16: Verify that the@/public/*path removal doesn't break admin app imports.A past review flagged the removal of the
"public/*"path mapping as breaking asset imports, though the example cited was fromapps/web/. For the admin app, verify whetherapps/admin/itself contains imports using@/public/*or/public/*paths before confirming this change is safe.Run the following script to check if the admin app has any imports relying on the removed
@/public/*mapping:If the admin app does use
@/public/*imports, restore the mapping:"paths": { "@/app/*": ["app/*"], "@/*": ["core/*"], "@/plane-admin/*": ["ce/*"], + "public/*": ["public/*"], "@/styles/*": ["styles/*"] }apps/admin/ce/components/authentication/authentication-modes.tsx (1)
13-18: Verify the asset paths exist in the new location.The asset imports have been migrated from public assets to local
@/app/assets/logos/with Vite's?urlsuffix. Ensure all these logo files exist at the specified paths.pnpm-workspace.yaml (3)
1-37: Address the past formatting concern before merging.A previous reviewer (sriramveeraghanta) flagged that your formatter uses double quotes and requested a revert of the file changes. This PR still appears to have formatting changes. Please either:
- Confirm that the formatter configuration has been updated to accept this format, or
- Revert the formatting to match your project's formatter expectations.
7-33: Verify dependency versions are compatible across the workspace.Several dependencies have been updated to newer versions that could affect other packages in the workspace:
- TypeScript 5.8.3: Ensure all workspace packages support TS 5.x (breaking changes possible)
- React 18.3.1 & react-dom 18.3.1: Confirm compatibility with existing apps
- Vite 7.1.7: New major version; verify build scripts and configurations are compatible
- mobx 6.12.0, mobx-react 9.1.1, mobx-utils 6.0.8: These are new to the workspace; confirm they're only used by admin or if they're shared dependencies
Additionally, verify that pinning these versions doesn't create conflicts with other parts of the monorepo (e.g., apps/web, apps/space).
2-5: Ensure hyphen list format is intentional and consistent.The YAML lists are formatted with hyphens (lines 2–5, 35–37), which is correct YAML syntax. However, given the past formatting concern, confirm this aligns with your project's formatting rules and linter configuration.
Also applies to: 35-37
apps/admin/app/(all)/(dashboard)/workspace/create/page.tsx (1)
22-23: LGTM: Route metadata follows React Router conventions.The meta export correctly provides page title metadata for the React Router migration.
apps/admin/app/(all)/(dashboard)/ai/page.tsx (1)
46-47: LGTM: Route metadata correctly defined.The meta export follows the established pattern for React Router page metadata.
apps/admin/app/types/next-link.d.ts (2)
2-11: Type definition structure is sound.The ambient module correctly declares Next.js Link compatibility types, extending anchor props with Next.js-specific options. This provides type safety for the migration compatibility layer.
1-12: Verify href type completeness for migration needs.The
hrefproperty is typed asstring, but Next.js Link also acceptsUrlObject(from 'url'). Ensure your codebase doesn't pass URL objects to Link components.Run this script to check for URL object usage in Link components:
apps/admin/vite.config.ts (1)
35-43: Usebuild.ssrfor the SSR entry;rollupOptions.inputis not the SSR entry point.Vite expects
build.ssrfor SSR builds. UsingrollupOptions.inputhere can yield a wrong or missing server bundle.- rollupOptions: - isSsrBuild && enableSsrBuild - ? { - input: path.resolve(__dirname, "server/app.ts"), - } - : undefined, + ssr: isSsrBuild && enableSsrBuild + ? path.resolve(__dirname, "server/app.ts") + : undefined,Please verify locally:
- Run:
pnpm vite build --ssr apps/admin/server/app.ts(or your equivalent) and confirm the server output is emitted whenADMIN_ENABLE_SSR_BUILD=true.packages/services/package.json (2)
6-10: ESM migration and exports structure look good.The addition of
"type": "module"and the updated import/require mappings follow standard dual-package conventions for ESM-first libraries.
25-35: Clarify the purpose of new file-handling dependencies.The PR adds several binary/media handling libraries (
fflate,ieee754,strtok3,token-types,@tokenizer/inflate,@borewit/text-codec,uint8array-extras) and upgradesfile-typeto^21.0.0. These appear orthogonal to the admin React Router + Vite migration described in the PR objectives.Confirm whether these dependencies are:
- Intentionally introduced as part of this PR (if so, document their usage)
- Accidentally included from another feature branch
- Required by an updated transitive dependency
Also verify that these additions do not bloat the services package unnecessarily.
apps/admin/app/components/404.tsx (1)
1-6: LGTM!The imports are clean and the SVG asset import using Vite's
?urlsuffix is the correct approach for static assets.apps/admin/react-router.config.ts (2)
1-2: LGTM!The import is correct for React Router configuration.
5-5: Verify basename behavior when environment variable is undefined.The
basenameis set toprocess.env.NEXT_PUBLIC_ADMIN_BASE_PATH, which will beundefinedif the environment variable is not set. While React Router typically defaults to"/"when basename is undefined, please verify that:
- The environment variable is properly configured in all deployment environments (development, staging, production)
- The app routes correctly when the variable is not set (if that's an expected scenario)
- Consider providing an explicit fallback if undefined is not the desired behavior:
- basename: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH, + basename: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "/",Run the following script to check how this environment variable is used across the admin app:
apps/admin/app/compat/next/helper.ts (1)
6-33: Verify trailing slash enforcement is intentional across all routes.The function enforces trailing slashes on all URLs (except root). React Router typically works with or without trailing slashes, so this normalization must be applied consistently throughout the app to avoid routing mismatches.
Verify that:
- All route definitions in
apps/admin/app/routes.tsare consistent with this trailing slash convention- Existing links and navigation calls throughout the codebase work correctly with this normalization
- The React Router configuration expects trailing slashes
Run this script to check for potential inconsistencies:
apps/admin/app/routes.ts (1)
18-19: Verify the 404 component path and catch-all route behavior.The catch-all route uses a relative path to the 404 component. Ensure this path is correct and the component is exported properly.
Verify that:
- The 404 component exists at the specified path
- It's exported as a default export
- The catch-all route correctly handles unmatched paths
apps/admin/app/compat/next/navigation.ts (1)
11-12: Verify trailing slash enforcement doesn't break existing routes.The navigation shim applies
ensureTrailingSlashto allpushandreplacecalls. This must be consistent with how routes are defined and how existing navigation logic expects paths to be formatted.Search for existing navigation calls to verify they work correctly with trailing slashes:
apps/admin/server/app.ts (1)
22-25: Document the trust proxy assumption.Setting
trust proxy: trueunconditionally tells Express to honorX-Forwarded-*headers from any upstream. This is safe when the admin app is always behind nginx (as your comment suggests), but if it's ever exposed directly, clients could spoof those headers. Consider documenting this deployment requirement or making it conditional based on an environment flag.apps/admin/server.mjs (2)
67-70: Origin string construction assumes valid PORTLine 68 constructs the origin URL using
PORT, which will produce invalid output likehttp://example.com:NaNif the PORT derivation issue (line 26) is not fixed. Additionally, for standard ports (80/443), explicitly including the port in the origin URL is unusual, though not incorrect.This will be resolved once the PORT derivation logic is corrected.
46-57: Development SSR middleware looks correctThe Vite SSR setup properly loads the server app module and handles errors with stack trace fixing. Ensure that
server/app.tsexports a function namedappthat accepts(req, res, next)parameters as expected on line 49.apps/admin/package.json (2)
7-7: Verify ES module compatibility across the codebase.Adding
"type": "module"enables ES modules for.jsand.tsfiles in this workspace. Ensure all entry points, imports, and tooling (e.g., server.mjs, Vite config, test runners) are compatible with ESM-only mode; review any.cjsfiles,require()calls, or CommonJS-dependent dependencies.
9-15: Script refactoring aligns well with React Router + Vite architecture.The migration from Next.js to React Router is reflected cleanly: custom
server.mjsentry points,react-router buildreplacing Next.js build, andreact-router typegenfor type safety are appropriate. Theservepackage for static serving andcross-envfor environment consistency are solid choices.
| refresh: () => { | ||
| location.reload(); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refresh() causes full page reload and loses client state.
location.reload() triggers a full page refresh, which differs from Next.js router.refresh() behavior. Next.js router.refresh() re-fetches data while preserving client state, but this implementation loses all React state, SWR cache, and in-memory data.
Consider these alternatives:
- If route-level data refetch is needed: Implement using React Router's
revalidatepattern or a custom invalidation mechanism - If this is a stub: Document the limitation clearly with a comment
- If full reload is intentional: Keep current implementation but add a comment explaining this is not equivalent to Next.js behavior
Example for option 2:
refresh: () => {
+ // NOTE: This is a full page reload and NOT equivalent to Next.js router.refresh()
+ // which re-fetches data while preserving client state. Migrate code using
+ // router.refresh() to use React Router's revalidation pattern instead.
location.reload();
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| refresh: () => { | |
| location.reload(); | |
| }, | |
| refresh: () => { | |
| // NOTE: This is a full page reload and NOT equivalent to Next.js router.refresh() | |
| // which re-fetches data while preserving client state. Migrate code using | |
| // router.refresh() to use React Router's revalidation pattern instead. | |
| location.reload(); | |
| }, |
🤖 Prompt for AI Agents
In apps/admin/app/compat/next/navigation.ts around lines 15 to 17, the refresh()
implementation calls location.reload() which forces a full page reload and loses
client React state, SWR cache and other in-memory data; replace this with a
non-destructive refresh strategy (preferably call the app's router.refresh() or
a revalidation/invalidation function that re-fetches data without unmounting
components), or if route-level revalidation isn’t available yet, add a clear
comment that this is a temporary stub and that full reload is intentional;
choose one: (A) wire into the framework's revalidate/refresh API to preserve
client state, (B) document as a stub with a TODO explaining limitations, or (C)
keep location.reload() but add a comment stating it intentionally forces a full
reload and is not equivalent to Next.js router.refresh().
| <Link to="/general/"> | ||
| <span className="flex justify-center py-4"> | ||
| <Button variant="neutral-primary" size="md"> | ||
| Go to general settings | ||
| </Button> | ||
| </span> | ||
| </Link> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix nested interactive elements accessibility issue.
Wrapping a Button inside a Link creates nested interactive elements, which is an accessibility anti-pattern. This can confuse screen readers and keyboard navigation, as it's unclear which element should receive focus and handle the click event.
Consider one of these solutions:
Option 1: Style the Link as a button
- <Link to="/general/">
- <span className="flex justify-center py-4">
- <Button variant="neutral-primary" size="md">
- Go to general settings
- </Button>
- </span>
- </Link>
+ <div className="flex justify-center py-4">
+ <Button variant="neutral-primary" size="md" asChild>
+ <Link to="/general/">Go to general settings</Link>
+ </Button>
+ </div>Option 2: If Button doesn't support asChild, use Link with button styling
- <Link to="/general/">
- <span className="flex justify-center py-4">
- <Button variant="neutral-primary" size="md">
- Go to general settings
- </Button>
- </span>
- </Link>
+ <div className="flex justify-center py-4">
+ <Link
+ to="/general/"
+ className="inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium"
+ >
+ Go to general settings
+ </Link>
+ </div>Note: Check if the @plane/propel/button component supports an asChild prop (common in Radix-based component libraries) for composition with routing links.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Link to="/general/"> | |
| <span className="flex justify-center py-4"> | |
| <Button variant="neutral-primary" size="md"> | |
| Go to general settings | |
| </Button> | |
| </span> | |
| </Link> | |
| <div className="flex justify-center py-4"> | |
| <Link | |
| to="/general/" | |
| className="inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium" | |
| > | |
| Go to general settings | |
| </Link> | |
| </div> |
🤖 Prompt for AI Agents
In apps/admin/app/components/404.tsx around lines 22 to 28, the Button is nested
inside a Link which creates nested interactive elements; to fix, either (A) use
the Button's composition API (e.g., asChild) so the Button renders the Link
(check if @plane/propel/button supports asChild and if so render Button as the
outer component with Link as its child), or (B) remove the Button component and
style the Link to look and behave like the button (apply the Button
classes/variant/size to the Link and ensure it has role/button and proper
keyboard handling), then remove the nested interactive element to restore
correct accessibility and focus behavior.
| return { | ||
| define: { | ||
| "process.env": JSON.stringify(publicEnv), | ||
| }, | ||
| build: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set base from NEXT_PUBLIC_ADMIN_BASE_PATH for subpath deployments.
Without base, asset URLs will point to /assets/... and break when the app is served under a subpath (e.g., /admin). You already expose NEXT_PUBLIC_ADMIN_BASE_PATH; wire it in.
return {
+ base: publicEnv.NEXT_PUBLIC_ADMIN_BASE_PATH || "/",Note: ensure this path ends with a trailing slash in your env (e.g., /admin/) to generate correct asset URLs.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/admin/vite.config.ts around lines 31–35, the Vite config currently
doesn't set the base path so asset URLs break when the app is served under a
subpath; update the config to read NEXT_PUBLIC_ADMIN_BASE_PATH from the
environment and assign it to the Vite `base` option (use a safe fallback of '/'
if the env var is unset) and ensure the env value includes a trailing slash
(e.g., '/admin/').
| "eslint-plugin-import": "^2.32.0", | ||
| "eslint-plugin-react": "^7.37.5", | ||
| "eslint-plugin-react-hooks": "^6.1.1", | ||
| "typescript": "catalog:" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete or malformed TypeScript version reference.
Line 21 shows "typescript": "catalog:" which appears to be incomplete. If this is a monorepo version catalog reference, it needs a proper identifier (e.g., "catalog:default" or similar). If it's meant to be a semantic version constraint, it should specify a version range.
Please verify and correct the TypeScript version reference.
🤖 Prompt for AI Agents
In packages/eslint-config/package.json around line 21, the TypeScript version
entry is malformed as "typescript": "catalog:"; replace this with a valid
version reference—either a proper version-catalog identifier used by your
monorepo (for example "catalog:default" or the exact catalog key your build
uses) or a semantic version string (for example "^5.1.0")—so the package.json
contains a correct, resolvable TypeScript reference consistent with your repo's
dependency management.
Description
This pull request migrates the admin application from Next.js to a React Router + Vite architecture.
It replaces server-side rendering with a modern client-side routing setup and static asset build flow.
Key updates include:
next/link,next/image, and related utilities to ensure smooth migration.Type of Change
Screenshots and Media (if applicable)
N/A — migration affects build and routing structure, not UI appearance.
Test Scenarios
References
https://app.plane.so/plane/browse/WEB-5169/
Summary by CodeRabbit
New Features
Bug Fixes & Improvements
Chores