diff --git a/.gitignore b/.gitignore index e32feb91b1d..7e16a33e5ff 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ dev-editor storybook-static CLAUDE.md +AGENTS.md build/ .react-router/ diff --git a/apps/admin/app/(all)/(dashboard)/ai/form.tsx b/apps/admin/app/(all)/(dashboard)/ai/form.tsx index 64970a547aa..5b931e9d780 100644 --- a/apps/admin/app/(all)/(dashboard)/ai/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/ai/form.tsx @@ -1,5 +1,5 @@ "use client"; -import type { FC } from "react"; + import { useForm } from "react-hook-form"; import { Lightbulb } from "lucide-react"; import { Button } from "@plane/propel/button"; @@ -17,7 +17,7 @@ type IInstanceAIForm = { type AIFormValues = Record; -export const InstanceAIForm: FC = (props) => { +export const InstanceAIForm: React.FC = (props) => { const { config } = props; // store const { updateInstanceConfigurations } = useInstance(); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx index ae0f54c4f07..86ab729b8a9 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/github/form.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useState } from "react"; import { isEmpty } from "lodash-es"; import Link from "next/link"; @@ -29,7 +28,7 @@ type Props = { type GithubConfigFormValues = Record; -export const InstanceGithubConfigForm: FC = (props) => { +export const InstanceGithubConfigForm: React.FC = (props) => { const { config } = props; // states const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx index 91e4ee8eca5..f8ffc6c35e9 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/gitlab/form.tsx @@ -1,4 +1,3 @@ -import type { FC } from "react"; import { useState } from "react"; import { isEmpty } from "lodash-es"; import Link from "next/link"; @@ -25,7 +24,7 @@ type Props = { type GitlabConfigFormValues = Record; -export const InstanceGitlabConfigForm: FC = (props) => { +export const InstanceGitlabConfigForm: React.FC = (props) => { const { config } = props; // states const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false); diff --git a/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx b/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx index d9c3646b734..f3235f6b70f 100644 --- a/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/authentication/google/form.tsx @@ -1,5 +1,5 @@ "use client"; -import type { FC } from "react"; + import { useState } from "react"; import { isEmpty } from "lodash-es"; import Link from "next/link"; @@ -27,7 +27,7 @@ type Props = { type GoogleConfigFormValues = Record; -export const InstanceGoogleConfigForm: FC = (props) => { +export const InstanceGoogleConfigForm: React.FC = (props) => { const { config } = props; // states const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false); diff --git a/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx b/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx index 450a5f4e938..2c00311b79e 100644 --- a/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx +++ b/apps/admin/app/(all)/(dashboard)/email/email-config-form.tsx @@ -1,7 +1,6 @@ "use client"; -import type { FC } from "react"; -import React, { useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { useForm } from "react-hook-form"; // types import { Button } from "@plane/propel/button"; @@ -31,7 +30,7 @@ const EMAIL_SECURITY_OPTIONS: { [key in TEmailSecurityKeys]: string } = { NONE: "No email security", }; -export const InstanceEmailForm: FC = (props) => { +export const InstanceEmailForm: React.FC = (props) => { const { config } = props; // states const [isSendTestEmailModalOpen, setIsSendTestEmailModalOpen] = useState(false); diff --git a/apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx b/apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx index 09117096074..89043fcc185 100644 --- a/apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx +++ b/apps/admin/app/(all)/(dashboard)/email/test-email-modal.tsx @@ -1,5 +1,4 @@ -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState, Fragment } from "react"; import { Dialog, Transition } from "@headlessui/react"; // plane imports import { Button } from "@plane/propel/button"; @@ -20,7 +19,7 @@ enum ESendEmailSteps { const instanceService = new InstanceService(); -export const SendTestEmailModal: FC = (props) => { +export const SendTestEmailModal: React.FC = (props) => { const { isOpen, handleClose } = props; // state @@ -62,10 +61,10 @@ export const SendTestEmailModal: FC = (props) => { }; return ( - + = (props) => {
= observer((props) => { +export const GeneralConfigurationForm: React.FC = observer((props) => { const { instance, instanceAdmins } = props; // hooks const { instanceConfigurations, updateInstanceInfo, updateInstanceConfigurations } = useInstance(); diff --git a/apps/admin/app/(all)/(dashboard)/general/intercom.tsx b/apps/admin/app/(all)/(dashboard)/general/intercom.tsx index a6f17d629ad..61a417c45b4 100644 --- a/apps/admin/app/(all)/(dashboard)/general/intercom.tsx +++ b/apps/admin/app/(all)/(dashboard)/general/intercom.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useState } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; @@ -14,7 +13,7 @@ type TIntercomConfig = { isTelemetryEnabled: boolean; }; -export const IntercomConfig: FC = observer((props) => { +export const IntercomConfig: React.FC = observer((props) => { const { isTelemetryEnabled } = props; // hooks const { instanceConfigurations, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance(); diff --git a/apps/admin/app/(all)/(dashboard)/header.tsx b/apps/admin/app/(all)/(dashboard)/header.tsx index 900f5abeddf..049c3083429 100644 --- a/apps/admin/app/(all)/(dashboard)/header.tsx +++ b/apps/admin/app/(all)/(dashboard)/header.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { observer } from "mobx-react"; import { usePathname } from "next/navigation"; import { Menu, Settings } from "lucide-react"; @@ -11,7 +10,7 @@ import { BreadcrumbLink } from "@/components/common/breadcrumb-link"; // hooks import { useTheme } from "@/hooks/store"; -export const HamburgerToggle: FC = observer(() => { +export const HamburgerToggle = observer(() => { const { isSidebarCollapsed, toggleSidebar } = useTheme(); return (
{ ); }); -export const AdminHeader: FC = observer(() => { +export const AdminHeader = observer(() => { const pathName = usePathname(); const getHeaderTitle = (pathName: string) => { diff --git a/apps/admin/app/(all)/(dashboard)/image/form.tsx b/apps/admin/app/(all)/(dashboard)/image/form.tsx index f6adcaee4b1..da105bb3e62 100644 --- a/apps/admin/app/(all)/(dashboard)/image/form.tsx +++ b/apps/admin/app/(all)/(dashboard)/image/form.tsx @@ -1,5 +1,4 @@ "use client"; -import type { FC } from "react"; import { useForm } from "react-hook-form"; import { Button } from "@plane/propel/button"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; @@ -15,7 +14,7 @@ type IInstanceImageConfigForm = { type ImageConfigFormValues = Record; -export const InstanceImageConfigForm: FC = (props) => { +export const InstanceImageConfigForm: React.FC = (props) => { const { config } = props; // store hooks const { updateInstanceConfigurations } = useInstance(); diff --git a/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx b/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx index cf479119015..157c3240b31 100644 --- a/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx +++ b/apps/admin/app/(all)/(dashboard)/sidebar-help-section.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useState, useRef } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; @@ -35,7 +34,7 @@ const helpOptions = [ }, ]; -export const AdminSidebarHelpSection: FC = observer(() => { +export const AdminSidebarHelpSection: React.FC = observer(() => { // states const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false); // store diff --git a/apps/admin/app/(all)/(dashboard)/sidebar.tsx b/apps/admin/app/(all)/(dashboard)/sidebar.tsx index e37d6eb5c69..fb8091df03a 100644 --- a/apps/admin/app/(all)/(dashboard)/sidebar.tsx +++ b/apps/admin/app/(all)/(dashboard)/sidebar.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useEffect, useRef } from "react"; import { observer } from "mobx-react"; // plane helpers @@ -12,7 +11,7 @@ import { AdminSidebarDropdown } from "./sidebar-dropdown"; import { AdminSidebarHelpSection } from "./sidebar-help-section"; import { AdminSidebarMenu } from "./sidebar-menu"; -export const AdminSidebar: FC = observer(() => { +export const AdminSidebar = observer(() => { // store const { isSidebarCollapsed, toggleSidebar } = useTheme(); diff --git a/apps/admin/app/(all)/(home)/auth-banner.tsx b/apps/admin/app/(all)/(home)/auth-banner.tsx index bb0adaa4d0b..2af42ed8e4d 100644 --- a/apps/admin/app/(all)/(home)/auth-banner.tsx +++ b/apps/admin/app/(all)/(home)/auth-banner.tsx @@ -1,4 +1,3 @@ -import type { FC } from "react"; import { Info } from "lucide-react"; // plane constants import type { TAdminAuthErrorInfo } from "@plane/constants"; @@ -10,7 +9,7 @@ type TAuthBanner = { handleBannerData?: (bannerData: TAdminAuthErrorInfo | undefined) => void; }; -export const AuthBanner: FC = (props) => { +export const AuthBanner: React.FC = (props) => { const { bannerData, handleBannerData } = props; if (!bannerData) return <>; diff --git a/apps/admin/app/(all)/(home)/sign-in-form.tsx b/apps/admin/app/(all)/(home)/sign-in-form.tsx index 2049bda6154..56f0ca73518 100644 --- a/apps/admin/app/(all)/(home)/sign-in-form.tsx +++ b/apps/admin/app/(all)/(home)/sign-in-form.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useEffect, useMemo, useState } from "react"; import { useSearchParams } from "next/navigation"; import { Eye, EyeOff } from "lucide-react"; @@ -46,7 +45,7 @@ const defaultFromData: TFormData = { password: "", }; -export const InstanceSignInForm: FC = () => { +export const InstanceSignInForm: React.FC = () => { // search params const searchParams = useSearchParams(); const emailParam = searchParams.get("email") || undefined; diff --git a/apps/admin/app/(all)/instance.provider.tsx b/apps/admin/app/(all)/instance.provider.tsx index 19e15ec529d..8a5cadcd120 100644 --- a/apps/admin/app/(all)/instance.provider.tsx +++ b/apps/admin/app/(all)/instance.provider.tsx @@ -1,14 +1,9 @@ -import type { FC, ReactNode } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; // hooks import { useInstance } from "@/hooks/store"; -type InstanceProviderProps = { - children: ReactNode; -}; - -export const InstanceProvider: FC = observer((props) => { +export const InstanceProvider = observer>((props) => { const { children } = props; // store hooks const { fetchInstanceInfo } = useInstance(); diff --git a/apps/admin/app/(all)/store.provider.tsx b/apps/admin/app/(all)/store.provider.tsx index 648a37119b8..35c6cbc0181 100644 --- a/apps/admin/app/(all)/store.provider.tsx +++ b/apps/admin/app/(all)/store.provider.tsx @@ -1,6 +1,5 @@ "use client"; -import type { ReactNode } from "react"; import { createContext } from "react"; // plane admin store import { RootStore } from "@/plane-admin/store/root.store"; @@ -24,7 +23,7 @@ function initializeStore(initialData = {}) { } export type StoreProviderProps = { - children: ReactNode; + children: React.ReactNode; // eslint-disable-next-line @typescript-eslint/no-explicit-any initialState?: any; }; diff --git a/apps/admin/app/(all)/user.provider.tsx b/apps/admin/app/(all)/user.provider.tsx index e026c31da31..5ff9e88c7e1 100644 --- a/apps/admin/app/(all)/user.provider.tsx +++ b/apps/admin/app/(all)/user.provider.tsx @@ -1,17 +1,12 @@ "use client"; -import type { FC, ReactNode } from "react"; import { useEffect } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; // hooks import { useInstance, useTheme, useUser } from "@/hooks/store"; -interface IUserProvider { - children: ReactNode; -} - -export const UserProvider: FC = observer(({ children }) => { +export const UserProvider = observer>(({ children }) => { // hooks const { isSidebarCollapsed, toggleSidebar } = useTheme(); const { currentUser, fetchCurrentUser } = useUser(); diff --git a/apps/admin/core/components/authentication/authentication-method-card.tsx b/apps/admin/core/components/authentication/authentication-method-card.tsx index df8e6dba637..c1f613a96b2 100644 --- a/apps/admin/core/components/authentication/authentication-method-card.tsx +++ b/apps/admin/core/components/authentication/authentication-method-card.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; // helpers import { cn } from "@plane/utils"; @@ -14,7 +13,7 @@ type Props = { unavailable?: boolean; }; -export const AuthenticationMethodCard: FC = (props) => { +export const AuthenticationMethodCard: React.FC = (props) => { const { name, description, icon, config, disabled = false, withBorder = true, unavailable = false } = props; return ( diff --git a/apps/admin/core/components/authentication/gitlab-config.tsx b/apps/admin/core/components/authentication/gitlab-config.tsx index 6f0294c3cdc..d491ec14542 100644 --- a/apps/admin/core/components/authentication/gitlab-config.tsx +++ b/apps/admin/core/components/authentication/gitlab-config.tsx @@ -1,6 +1,5 @@ "use client"; -import React from "react"; import { observer } from "mobx-react"; import Link from "next/link"; // icons diff --git a/apps/admin/core/components/authentication/google-config.tsx b/apps/admin/core/components/authentication/google-config.tsx index ae0cecf338f..a87c6027e55 100644 --- a/apps/admin/core/components/authentication/google-config.tsx +++ b/apps/admin/core/components/authentication/google-config.tsx @@ -1,6 +1,5 @@ "use client"; -import React from "react"; import { observer } from "mobx-react"; import Link from "next/link"; // icons diff --git a/apps/admin/core/components/common/empty-state.tsx b/apps/admin/core/components/common/empty-state.tsx index 4bf291f5c57..dba49f60555 100644 --- a/apps/admin/core/components/common/empty-state.tsx +++ b/apps/admin/core/components/common/empty-state.tsx @@ -7,7 +7,7 @@ import { Button } from "@plane/propel/button"; type Props = { title: string; description?: React.ReactNode; - image?: any; + image?: string; primaryButton?: { icon?: any; text: string; diff --git a/apps/admin/core/components/instance/setup-form.tsx b/apps/admin/core/components/instance/setup-form.tsx index a4d59b6895a..eae4c6d87b8 100644 --- a/apps/admin/core/components/instance/setup-form.tsx +++ b/apps/admin/core/components/instance/setup-form.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useEffect, useMemo, useState } from "react"; import { useSearchParams } from "next/navigation"; // icons @@ -54,7 +53,7 @@ const defaultFromData: TFormData = { is_telemetry_enabled: true, }; -export const InstanceSetupForm: FC = (props) => { +export const InstanceSetupForm: React.FC = (props) => { const {} = props; // search params const searchParams = useSearchParams(); diff --git a/apps/admin/core/utils/public-asset.ts b/apps/admin/core/utils/public-asset.ts new file mode 100644 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/apps/admin/core/utils/public-asset.ts @@ -0,0 +1 @@ +export {}; diff --git a/apps/space/.eslintrc.js b/apps/space/.eslintrc.cjs similarity index 77% rename from apps/space/.eslintrc.js rename to apps/space/.eslintrc.cjs index a0bc76d5d9a..af3135e6a54 100644 --- a/apps/space/.eslintrc.js +++ b/apps/space/.eslintrc.cjs @@ -1,9 +1,14 @@ module.exports = { root: true, extends: ["@plane/eslint-config/next.js"], + ignorePatterns: [ + "build/**", + "dist/**", + ".vite/**", + ], rules: { "no-duplicate-imports": "off", - "import/no-duplicates": ["error", { "prefer-inline": false }], + "import/no-duplicates": ["error", {"prefer-inline": false}], "import/consistent-type-specifier-style": ["error", "prefer-top-level"], "@typescript-eslint/no-import-type-side-effects": "error", "@typescript-eslint/consistent-type-imports": [ @@ -16,3 +21,5 @@ module.exports = { ], }, }; + + diff --git a/apps/space/.gitignore b/apps/space/.gitignore index bc7846c3c41..5449b293ac8 100644 --- a/apps/space/.gitignore +++ b/apps/space/.gitignore @@ -12,6 +12,9 @@ /.next/ /out/ +# react-router +/.react-router/ + # production /build diff --git a/apps/space/Dockerfile.space b/apps/space/Dockerfile.space index 570511b9d30..89c02ae962c 100644 --- a/apps/space/Dockerfile.space +++ b/apps/space/Dockerfile.space @@ -1,103 +1,86 @@ -# syntax=docker/dockerfile:1.7 FROM node:22-alpine AS base -# Setup pnpm package manager with corepack and configure global bin directory for caching +WORKDIR /app + +ENV TURBO_TELEMETRY_DISABLED=1 ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable +ENV CI=1 + +RUN corepack enable pnpm + +# =========================================================================== # -# ***************************************************************************** -# STAGE 1: Build the project -# ***************************************************************************** FROM base AS builder -RUN apk add --no-cache libc6-compat -WORKDIR /app -ARG TURBO_VERSION=2.5.6 -RUN corepack enable pnpm && pnpm add -g turbo@${TURBO_VERSION} +RUN pnpm add -g turbo@2.5.8 + COPY . . +# Create a pruned workspace for just the space app RUN turbo prune --scope=space --docker -# ***************************************************************************** -# STAGE 2: Install dependencies & build the project -# ***************************************************************************** -FROM base AS installer - -RUN apk add --no-cache libc6-compat -WORKDIR /app +# =========================================================================== # -COPY .gitignore .gitignore -COPY --from=builder /app/out/json/ . -COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml -RUN corepack enable pnpm -RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm fetch --store-dir=/pnpm/store +FROM base AS installer -COPY --from=builder /app/out/full/ . -COPY turbo.json turbo.json -RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store +# Build in production mode; we still install dev deps explicitly below +ENV NODE_ENV=production +# Public envs required at build time (pick up via process.env) 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 ARG NEXT_PUBLIC_SPACE_BASE_URL="" ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL - ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces" ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH +ARG NEXT_PUBLIC_LIVE_BASE_URL="" +ENV NEXT_PUBLIC_LIVE_BASE_URL=$NEXT_PUBLIC_LIVE_BASE_URL +ARG NEXT_PUBLIC_LIVE_BASE_PATH="/live" +ENV NEXT_PUBLIC_LIVE_BASE_PATH=$NEXT_PUBLIC_LIVE_BASE_PATH + ARG NEXT_PUBLIC_WEB_BASE_URL="" ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL +ARG NEXT_PUBLIC_WEB_BASE_PATH="" +ENV NEXT_PUBLIC_WEB_BASE_PATH=$NEXT_PUBLIC_WEB_BASE_PATH -ENV NEXT_TELEMETRY_DISABLED=1 -ENV TURBO_TELEMETRY_DISABLED=1 - -RUN pnpm turbo run build --filter=space +ARG NEXT_PUBLIC_WEBSITE_URL="https://plane.so" +ENV NEXT_PUBLIC_WEBSITE_URL=$NEXT_PUBLIC_WEBSITE_URL +ARG NEXT_PUBLIC_SUPPORT_EMAIL="support@plane.so" +ENV NEXT_PUBLIC_SUPPORT_EMAIL=$NEXT_PUBLIC_SUPPORT_EMAIL -# ***************************************************************************** -# STAGE 3: Copy the project and start it -# ***************************************************************************** -FROM base AS runner -WORKDIR /app - -# Don't run production as root -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs -USER nextjs - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=installer /app/apps/space/.next/standalone ./ -COPY --from=installer /app/apps/space/.next/static ./apps/space/.next/static -COPY --from=installer /app/apps/space/public ./apps/space/public - -ARG NEXT_PUBLIC_API_BASE_URL="" -ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL - -ARG NEXT_PUBLIC_ADMIN_BASE_URL="" -ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL +COPY .gitignore .gitignore +COPY --from=builder /app/out/json/ . +COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml -ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode" -ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH +# Fetch dependencies to cache store, then install offline with dev deps +RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm fetch --store-dir=/pnpm/store +COPY --from=builder /app/out/full/ . +COPY turbo.json turbo.json +RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store --prod=false -ARG NEXT_PUBLIC_SPACE_BASE_URL="" -ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL +# Build only the space package +RUN pnpm turbo run build --filter=space -ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces" -ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH +# =========================================================================== # -ARG NEXT_PUBLIC_WEB_BASE_URL="" -ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL +FROM nginx:1.27-alpine AS production -ENV NEXT_TELEMETRY_DISABLED=1 -ENV TURBO_TELEMETRY_DISABLED=1 +COPY apps/space/nginx/nginx.conf /etc/nginx/nginx.conf +COPY --from=installer /app/apps/space/build/client /usr/share/nginx/html/spaces EXPOSE 3000 -CMD ["node", "apps/space/server.js"] +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -fsS http://127.0.0.1:3000/ >/dev/null || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/space/app/[workspaceSlug]/[projectId]/page.ts b/apps/space/app/[workspaceSlug]/[projectId]/page.ts deleted file mode 100644 index 94c4152e45e..00000000000 --- a/apps/space/app/[workspaceSlug]/[projectId]/page.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { notFound, redirect } from "next/navigation"; -// plane imports -import { SitesProjectPublishService } from "@plane/services"; -import type { TProjectPublishSettings } from "@plane/types"; - -const publishService = new SitesProjectPublishService(); - -type Props = { - params: { - workspaceSlug: string; - projectId: string; - }; - searchParams: Record<"board" | "peekId", string | string[] | undefined>; -}; - -export default async function IssuesPage(props: Props) { - const { params, searchParams } = props; - // query params - const { workspaceSlug, projectId } = params; - const { board, peekId } = searchParams; - - let response: TProjectPublishSettings | undefined = undefined; - try { - response = await publishService.retrieveSettingsByProjectId(workspaceSlug, projectId); - } catch (error) { - console.error("Error fetching project publish settings:", error); - notFound(); - } - - let url = ""; - if (response?.entity_name === "project") { - url = `/issues/${response?.anchor}`; - const params = new URLSearchParams(); - if (board) params.append("board", String(board)); - if (peekId) params.append("peekId", String(peekId)); - if (params.toString()) url += `?${params.toString()}`; - redirect(url); - } else { - notFound(); - } -} diff --git a/apps/space/app/[workspaceSlug]/[projectId]/page.tsx b/apps/space/app/[workspaceSlug]/[projectId]/page.tsx new file mode 100644 index 00000000000..5049ef441e0 --- /dev/null +++ b/apps/space/app/[workspaceSlug]/[projectId]/page.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { redirect } from "react-router"; +import type { ClientLoaderFunctionArgs } from "react-router"; +// plane imports +import { SitesProjectPublishService } from "@plane/services"; +import type { TProjectPublishSettings } from "@plane/types"; +// components +import { LogoSpinner } from "@/components/common/logo-spinner"; + +const publishService = new SitesProjectPublishService(); + +export const clientLoader = async ({ params, request }: ClientLoaderFunctionArgs) => { + const { workspaceSlug, projectId } = params; + + // Validate required params + if (!workspaceSlug || !projectId) { + throw redirect("/404"); + } + + // Extract query params from the request URL + const url = new URL(request.url); + const board = url.searchParams.get("board"); + const peekId = url.searchParams.get("peekId"); + + let response: TProjectPublishSettings | undefined = undefined; + + try { + response = await publishService.retrieveSettingsByProjectId(workspaceSlug, projectId); + } catch { + throw redirect("/404"); + } + + if (response?.entity_name === "project") { + let redirectUrl = `/issues/${response?.anchor}`; + const urlParams = new URLSearchParams(); + if (board) urlParams.append("board", String(board)); + if (peekId) urlParams.append("peekId", String(peekId)); + if (urlParams.toString()) redirectUrl += `?${urlParams.toString()}`; + + throw redirect(redirectUrl); + } else { + throw redirect("/404"); + } +}; + +export default function IssuesPage() { + return ( +
+ +
+ ); +} diff --git a/apps/space/public/404.svg b/apps/space/app/assets/404.svg similarity index 100% rename from apps/space/public/404.svg rename to apps/space/app/assets/404.svg diff --git a/apps/space/public/auth/background-pattern-dark.svg b/apps/space/app/assets/auth/background-pattern-dark.svg similarity index 100% rename from apps/space/public/auth/background-pattern-dark.svg rename to apps/space/app/assets/auth/background-pattern-dark.svg diff --git a/apps/space/public/auth/background-pattern.svg b/apps/space/app/assets/auth/background-pattern.svg similarity index 100% rename from apps/space/public/auth/background-pattern.svg rename to apps/space/app/assets/auth/background-pattern.svg diff --git a/apps/space/public/favicon/apple-touch-icon.png b/apps/space/app/assets/favicon/apple-touch-icon.png similarity index 100% rename from apps/space/public/favicon/apple-touch-icon.png rename to apps/space/app/assets/favicon/apple-touch-icon.png diff --git a/apps/space/public/favicon/favicon-16x16.png b/apps/space/app/assets/favicon/favicon-16x16.png similarity index 100% rename from apps/space/public/favicon/favicon-16x16.png rename to apps/space/app/assets/favicon/favicon-16x16.png diff --git a/apps/space/public/favicon/favicon-32x32.png b/apps/space/app/assets/favicon/favicon-32x32.png similarity index 100% rename from apps/space/public/favicon/favicon-32x32.png rename to apps/space/app/assets/favicon/favicon-32x32.png diff --git a/apps/space/public/favicon/favicon.ico b/apps/space/app/assets/favicon/favicon.ico similarity index 100% rename from apps/space/public/favicon/favicon.ico rename to apps/space/app/assets/favicon/favicon.ico diff --git a/apps/space/public/favicon/site.webmanifest b/apps/space/app/assets/favicon/site.webmanifest similarity index 100% rename from apps/space/public/favicon/site.webmanifest rename to apps/space/app/assets/favicon/site.webmanifest diff --git a/apps/space/public/images/logo-spinner-dark.gif b/apps/space/app/assets/images/logo-spinner-dark.gif similarity index 100% rename from apps/space/public/images/logo-spinner-dark.gif rename to apps/space/app/assets/images/logo-spinner-dark.gif diff --git a/apps/space/public/images/logo-spinner-light.gif b/apps/space/app/assets/images/logo-spinner-light.gif similarity index 100% rename from apps/space/public/images/logo-spinner-light.gif rename to apps/space/app/assets/images/logo-spinner-light.gif diff --git a/apps/space/public/instance/instance-failure-dark.svg b/apps/space/app/assets/instance/instance-failure-dark.svg similarity index 100% rename from apps/space/public/instance/instance-failure-dark.svg rename to apps/space/app/assets/instance/instance-failure-dark.svg diff --git a/apps/space/public/instance/instance-failure.svg b/apps/space/app/assets/instance/instance-failure.svg similarity index 100% rename from apps/space/public/instance/instance-failure.svg rename to apps/space/app/assets/instance/instance-failure.svg diff --git a/apps/space/public/instance/intake-sent-dark.png b/apps/space/app/assets/instance/intake-sent-dark.png similarity index 100% rename from apps/space/public/instance/intake-sent-dark.png rename to apps/space/app/assets/instance/intake-sent-dark.png diff --git a/apps/space/public/instance/intake-sent-light.png b/apps/space/app/assets/instance/intake-sent-light.png similarity index 100% rename from apps/space/public/instance/intake-sent-light.png rename to apps/space/app/assets/instance/intake-sent-light.png diff --git a/apps/space/public/instance/plane-instance-not-ready.webp b/apps/space/app/assets/instance/plane-instance-not-ready.webp similarity index 100% rename from apps/space/public/instance/plane-instance-not-ready.webp rename to apps/space/app/assets/instance/plane-instance-not-ready.webp diff --git a/apps/space/public/instance/plane-takeoff.png b/apps/space/app/assets/instance/plane-takeoff.png similarity index 100% rename from apps/space/public/instance/plane-takeoff.png rename to apps/space/app/assets/instance/plane-takeoff.png diff --git a/apps/space/public/logos/gitea-logo.svg b/apps/space/app/assets/logos/gitea-logo.svg similarity index 100% rename from apps/space/public/logos/gitea-logo.svg rename to apps/space/app/assets/logos/gitea-logo.svg diff --git a/apps/space/public/logos/github-black.png b/apps/space/app/assets/logos/github-black.png similarity index 100% rename from apps/space/public/logos/github-black.png rename to apps/space/app/assets/logos/github-black.png diff --git a/apps/space/public/logos/github-dark.svg b/apps/space/app/assets/logos/github-dark.svg similarity index 100% rename from apps/space/public/logos/github-dark.svg rename to apps/space/app/assets/logos/github-dark.svg diff --git a/apps/space/public/logos/github-square.svg b/apps/space/app/assets/logos/github-square.svg similarity index 100% rename from apps/space/public/logos/github-square.svg rename to apps/space/app/assets/logos/github-square.svg diff --git a/apps/space/public/logos/github-white.svg b/apps/space/app/assets/logos/github-white.svg similarity index 100% rename from apps/space/public/logos/github-white.svg rename to apps/space/app/assets/logos/github-white.svg diff --git a/apps/space/public/logos/gitlab-logo.svg b/apps/space/app/assets/logos/gitlab-logo.svg similarity index 100% rename from apps/space/public/logos/gitlab-logo.svg rename to apps/space/app/assets/logos/gitlab-logo.svg diff --git a/apps/space/public/logos/google-logo.svg b/apps/space/app/assets/logos/google-logo.svg similarity index 100% rename from apps/space/public/logos/google-logo.svg rename to apps/space/app/assets/logos/google-logo.svg diff --git a/apps/space/public/plane-logo.svg b/apps/space/app/assets/plane-logo.svg similarity index 100% rename from apps/space/public/plane-logo.svg rename to apps/space/app/assets/plane-logo.svg diff --git a/apps/space/public/plane-logos/black-horizontal-with-blue-logo.png b/apps/space/app/assets/plane-logos/black-horizontal-with-blue-logo.png similarity index 100% rename from apps/space/public/plane-logos/black-horizontal-with-blue-logo.png rename to apps/space/app/assets/plane-logos/black-horizontal-with-blue-logo.png diff --git a/apps/space/public/plane-logos/blue-without-text-new.png b/apps/space/app/assets/plane-logos/blue-without-text-new.png similarity index 100% rename from apps/space/public/plane-logos/blue-without-text-new.png rename to apps/space/app/assets/plane-logos/blue-without-text-new.png diff --git a/apps/space/public/plane-logos/blue-without-text.png b/apps/space/app/assets/plane-logos/blue-without-text.png similarity index 100% rename from apps/space/public/plane-logos/blue-without-text.png rename to apps/space/app/assets/plane-logos/blue-without-text.png diff --git a/apps/space/public/plane-logos/white-horizontal-with-blue-logo.png b/apps/space/app/assets/plane-logos/white-horizontal-with-blue-logo.png similarity index 100% rename from apps/space/public/plane-logos/white-horizontal-with-blue-logo.png rename to apps/space/app/assets/plane-logos/white-horizontal-with-blue-logo.png diff --git a/apps/space/public/plane-logos/white-horizontal.svg b/apps/space/app/assets/plane-logos/white-horizontal.svg similarity index 100% rename from apps/space/public/plane-logos/white-horizontal.svg rename to apps/space/app/assets/plane-logos/white-horizontal.svg diff --git a/apps/space/public/project-not-published.svg b/apps/space/app/assets/project-not-published.svg similarity index 100% rename from apps/space/public/project-not-published.svg rename to apps/space/app/assets/project-not-published.svg diff --git a/apps/space/public/robots.txt b/apps/space/app/assets/robots.txt similarity index 100% rename from apps/space/public/robots.txt rename to apps/space/app/assets/robots.txt diff --git a/apps/space/public/something-went-wrong.svg b/apps/space/app/assets/something-went-wrong.svg similarity index 100% rename from apps/space/public/something-went-wrong.svg rename to apps/space/app/assets/something-went-wrong.svg diff --git a/apps/space/public/user-logged-in.svg b/apps/space/app/assets/user-logged-in.svg similarity index 100% rename from apps/space/public/user-logged-in.svg rename to apps/space/app/assets/user-logged-in.svg diff --git a/apps/space/app/compat/next/helper.ts b/apps/space/app/compat/next/helper.ts new file mode 100644 index 00000000000..fe1a984460d --- /dev/null +++ b/apps/space/app/compat/next/helper.ts @@ -0,0 +1,33 @@ +/** + * Ensures that a URL has a trailing slash while preserving query parameters and fragments + * @param url - The URL to process + * @returns The URL with a trailing slash added to the pathname (if not already present) + */ +export function ensureTrailingSlash(url: string): string { + try { + // Handle relative URLs by creating a URL object with a dummy base + const urlObj = new URL(url, "http://dummy.com"); + + // Don't modify root path + if (urlObj.pathname === "/") { + return url; + } + + // Add trailing slash if it doesn't exist + if (!urlObj.pathname.endsWith("/")) { + urlObj.pathname += "/"; + } + + // For relative URLs, return just the path + search + hash + if (url.startsWith("/")) { + return urlObj.pathname + urlObj.search + urlObj.hash; + } + + // For absolute URLs, return the full URL + return urlObj.toString(); + } catch (error) { + // If URL parsing fails, return the original URL + console.warn("Failed to parse URL for trailing slash enforcement:", url, error); + return url; + } +} diff --git a/apps/space/app/compat/next/image.tsx b/apps/space/app/compat/next/image.tsx new file mode 100644 index 00000000000..91de8b7810a --- /dev/null +++ b/apps/space/app/compat/next/image.tsx @@ -0,0 +1,14 @@ +"use client"; + +import React from "react"; + +// Minimal shim so code using next/image compiles under React Router + Vite +// without changing call sites. It just renders a native img. + +type NextImageProps = React.ImgHTMLAttributes & { + src: string; +}; + +const Image: React.FC = ({ src, alt = "", ...rest }) => {alt}; + +export default Image; diff --git a/apps/space/app/compat/next/link.tsx b/apps/space/app/compat/next/link.tsx new file mode 100644 index 00000000000..4f42363272f --- /dev/null +++ b/apps/space/app/compat/next/link.tsx @@ -0,0 +1,24 @@ +"use client"; + +import React from "react"; +import { Link as RRLink } from "react-router"; +import { ensureTrailingSlash } from "./helper"; + +type NextLinkProps = React.ComponentProps<"a"> & { + href: string; + replace?: boolean; + prefetch?: boolean; // next.js prop, ignored + scroll?: boolean; // next.js prop, ignored + shallow?: boolean; // next.js prop, ignored +}; + +const Link: React.FC = ({ + href, + replace, + prefetch: _prefetch, + scroll: _scroll, + shallow: _shallow, + ...rest +}) => ; + +export default Link; diff --git a/apps/space/app/compat/next/navigation.ts b/apps/space/app/compat/next/navigation.ts new file mode 100644 index 00000000000..b5bec8d6c88 --- /dev/null +++ b/apps/space/app/compat/next/navigation.ts @@ -0,0 +1,38 @@ +"use client"; + +import { useMemo } from "react"; +import { useLocation, useNavigate, useParams as useParamsRR, useSearchParams as useSearchParamsRR } from "react-router"; +import { ensureTrailingSlash } from "./helper"; + +export function useRouter() { + const navigate = useNavigate(); + return useMemo( + () => ({ + push: (to: string) => navigate(ensureTrailingSlash(to)), + replace: (to: string) => navigate(ensureTrailingSlash(to), { replace: true }), + back: () => navigate(-1), + forward: () => navigate(1), + refresh: () => { + location.reload(); + }, + prefetch: async (_to: string) => { + // no-op in this shim + }, + }), + [navigate] + ); +} + +export function usePathname(): string { + const { pathname } = useLocation(); + return pathname; +} + +export function useSearchParams(): URLSearchParams { + const [searchParams] = useSearchParamsRR(); + return searchParams; +} + +export function useParams() { + return useParamsRR(); +} diff --git a/apps/space/app/issues/[anchor]/client-layout.tsx b/apps/space/app/issues/[anchor]/client-layout.tsx index 398591c4d11..3b96abcab78 100644 --- a/apps/space/app/issues/[anchor]/client-layout.tsx +++ b/apps/space/app/issues/[anchor]/client-layout.tsx @@ -1,6 +1,7 @@ "use client"; import { observer } from "mobx-react"; +import { Outlet } from "react-router"; import useSWR from "swr"; // components import { LogoSpinner } from "@/components/common/logo-spinner"; @@ -10,14 +11,10 @@ import { IssuesNavbarRoot } from "@/components/issues/navbar"; // hooks import { usePublish, usePublishList } from "@/hooks/store/publish"; import { useIssueFilter } from "@/hooks/store/use-issue-filter"; +import type { Route } from "./+types/client-layout"; -type Props = { - children: React.ReactNode; - anchor: string; -}; - -export const IssuesClientLayout = observer((props: Props) => { - const { children, anchor } = props; +const IssuesClientLayout = observer((props: Route.ComponentProps) => { + const { anchor } = props.params; // store hooks const { fetchPublishSettings } = usePublishList(); const publishSettings = usePublish(anchor); @@ -57,9 +54,13 @@ export const IssuesClientLayout = observer((props: Props) => {
-
{children}
+
+ +
); }); + +export default IssuesClientLayout; diff --git a/apps/space/app/issues/[anchor]/layout.tsx b/apps/space/app/issues/[anchor]/layout.tsx index 46f187ddc49..46c6ed567af 100644 --- a/apps/space/app/issues/[anchor]/layout.tsx +++ b/apps/space/app/issues/[anchor]/layout.tsx @@ -1,6 +1,6 @@ "use server"; -import { IssuesClientLayout } from "./client-layout"; +import IssuesClientLayout from "./client-layout"; type Props = { children: React.ReactNode; @@ -9,6 +9,7 @@ type Props = { }; }; +// TODO: Convert into SSR in order to generate metadata export async function generateMetadata({ params }: Props) { const { anchor } = params; const DEFAULT_TITLE = "Plane"; @@ -53,5 +54,6 @@ export default async function IssuesLayout(props: Props) { const { children, params } = props; const { anchor } = params; - return {children}; + // return {children}; + return null; } diff --git a/apps/space/app/issues/[anchor]/page.tsx b/apps/space/app/issues/[anchor]/page.tsx index baff21324da..c4a4eb1bd00 100644 --- a/apps/space/app/issues/[anchor]/page.tsx +++ b/apps/space/app/issues/[anchor]/page.tsx @@ -1,7 +1,7 @@ "use client"; import { observer } from "mobx-react"; -import { useSearchParams } from "next/navigation"; +import { useParams, useSearchParams } from "next/navigation"; import useSWR from "swr"; // components import { IssuesLayoutsRoot } from "@/components/issues/issue-layouts"; @@ -10,16 +10,10 @@ import { usePublish } from "@/hooks/store/publish"; import { useLabel } from "@/hooks/store/use-label"; import { useStates } from "@/hooks/store/use-state"; -type Props = { - params: { - anchor: string; - }; -}; - -const IssuesPage = observer((props: Props) => { - const { params } = props; - const { anchor } = params; +const IssuesPage = observer(() => { // params + const params = useParams<{ anchor: string }>(); + const { anchor } = params; const searchParams = useSearchParams(); const peekId = searchParams.get("peekId") || undefined; // store diff --git a/apps/space/app/layout.tsx b/apps/space/app/layout.tsx deleted file mode 100644 index 05d54bd07ae..00000000000 --- a/apps/space/app/layout.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { Metadata } from "next"; -// helpers -import { SPACE_BASE_PATH } from "@plane/constants"; -// styles -import "@/styles/globals.css"; -// components -import { AppProvider } from "./provider"; - -export const metadata: Metadata = { - title: "Plane Publish | Make your Plane boards public with one-click", - description: "Plane Publish is a customer feedback management tool built on top of plane.so", - openGraph: { - title: "Plane Publish | Make your Plane boards public with one-click", - description: "Plane Publish is a customer feedback management tool built on top of plane.so", - url: "https://sites.plane.so/", - }, - keywords: - "software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration", - twitter: { - site: "@planepowers", - }, -}; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - - -
- - <>{children} - - - - ); -} diff --git a/apps/space/app/not-found.tsx b/apps/space/app/not-found.tsx index 9b6050ed11c..0ffe816c9d1 100644 --- a/apps/space/app/not-found.tsx +++ b/apps/space/app/not-found.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; // assets -import SomethingWentWrongImage from "public/something-went-wrong.svg"; +import SomethingWentWrongImage from "@/app/assets/something-went-wrong.svg?url"; const NotFound = () => (
diff --git a/apps/space/app/provider.tsx b/apps/space/app/providers.tsx similarity index 77% rename from apps/space/app/provider.tsx rename to apps/space/app/providers.tsx index 4a0a483ad1f..44093d26fc3 100644 --- a/apps/space/app/provider.tsx +++ b/apps/space/app/providers.tsx @@ -1,23 +1,18 @@ "use client"; -import type { ReactNode, FC } from "react"; import { ThemeProvider } from "next-themes"; // components import { TranslationProvider } from "@plane/i18n"; +import { AppProgressBar } from "@/lib/b-progress"; import { InstanceProvider } from "@/lib/instance-provider"; import { StoreProvider } from "@/lib/store-provider"; import { ToastProvider } from "@/lib/toast-provider"; -interface IAppProvider { - children: ReactNode; -} - -export const AppProvider: FC = (props) => { - const { children } = props; - +export function AppProviders({ children }: { children: React.ReactNode }) { return ( + {children} @@ -26,4 +21,4 @@ export const AppProvider: FC = (props) => { ); -}; +} diff --git a/apps/space/app/root.tsx b/apps/space/app/root.tsx new file mode 100644 index 00000000000..de37a7c244e --- /dev/null +++ b/apps/space/app/root.tsx @@ -0,0 +1,66 @@ +import { Links, Meta, Outlet, Scripts } from "react-router"; +import type { LinksFunction } from "react-router"; +// styles +import "@/styles/globals.css"; +// assets +import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url"; +import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url"; +import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url"; +import faviconIco from "@/app/assets/favicon/favicon.ico?url"; +// types +import type { Route } from "./+types/root"; +// local imports +import ErrorPage from "./error"; +import { AppProviders } from "./providers"; + +const APP_TITLE = "Plane Publish | Make your Plane boards public with one-click"; +const APP_DESCRIPTION = "Plane Publish is a customer feedback management tool built on top of plane.so"; + +export const links: LinksFunction = () => [ + { rel: "apple-touch-icon", sizes: "180x180", href: appleTouchIcon }, + { rel: "icon", type: "image/png", sizes: "32x32", href: favicon32 }, + { rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 }, + { rel: "shortcut icon", href: faviconIco }, + { rel: "manifest", href: `/site.webmanifest.json` }, +]; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + +
+ {children} + + + + ); +} + +export const meta: Route.MetaFunction = () => [ + { title: APP_TITLE }, + { name: "description", content: APP_DESCRIPTION }, + { property: "og:title", content: APP_TITLE }, + { property: "og:description", content: APP_DESCRIPTION }, + { property: "og:url", content: "https://sites.plane.so/" }, + { + name: "keywords", + content: + "software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration", + }, + { name: "twitter:site", content: "@planepowers" }, +]; + +export default function Root() { + return ; +} + +export function ErrorBoundary() { + return ; +} diff --git a/apps/space/app/routes.ts b/apps/space/app/routes.ts new file mode 100644 index 00000000000..bbfe623667b --- /dev/null +++ b/apps/space/app/routes.ts @@ -0,0 +1,10 @@ +import type { RouteConfig } from "@react-router/dev/routes"; +import { index, layout, route } from "@react-router/dev/routes"; + +export default [ + index("./page.tsx"), + route(":workspaceSlug/:projectId", "./[workspaceSlug]/[projectId]/page.tsx"), + layout("./issues/[anchor]/client-layout.tsx", [route("issues/:anchor", "./issues/[anchor]/page.tsx")]), + // Catch-all route for 404 handling + route("*", "./not-found.tsx"), +] satisfies RouteConfig; diff --git a/apps/space/app/types/next-image.d.ts b/apps/space/app/types/next-image.d.ts new file mode 100644 index 00000000000..c6026a2289b --- /dev/null +++ b/apps/space/app/types/next-image.d.ts @@ -0,0 +1,10 @@ +declare module "next/image" { + import type { FC, ImgHTMLAttributes } from "react"; + + type NextImageProps = ImgHTMLAttributes & { + src: string; + }; + + const Image: FC; + export default Image; +} diff --git a/apps/space/app/types/next-link.d.ts b/apps/space/app/types/next-link.d.ts new file mode 100644 index 00000000000..e39cc520554 --- /dev/null +++ b/apps/space/app/types/next-link.d.ts @@ -0,0 +1,14 @@ +declare module "next/link" { + import type { FC, ComponentProps } from "react"; + + type NextLinkProps = ComponentProps<"a"> & { + href: string; + replace?: boolean; + prefetch?: boolean; + scroll?: boolean; + shallow?: boolean; + }; + + const Link: FC; + export default Link; +} diff --git a/apps/space/app/types/next-navigation.d.ts b/apps/space/app/types/next-navigation.d.ts new file mode 100644 index 00000000000..67a80c4fae3 --- /dev/null +++ b/apps/space/app/types/next-navigation.d.ts @@ -0,0 +1,14 @@ +declare module "next/navigation" { + export function useRouter(): { + push: (url: string) => void; + replace: (url: string) => void; + back: () => void; + forward: () => void; + refresh: () => void; + prefetch: (url: string) => Promise; + }; + + export function usePathname(): string; + export function useSearchParams(): URLSearchParams; + export function useParams>(): T; +} diff --git a/apps/space/app/types/react-router-virtual.d.ts b/apps/space/app/types/react-router-virtual.d.ts new file mode 100644 index 00000000000..68caf20ae88 --- /dev/null +++ b/apps/space/app/types/react-router-virtual.d.ts @@ -0,0 +1,5 @@ +declare module "virtual:react-router/server-build" { + import type { ServerBuild } from "react-router"; + const serverBuild: ServerBuild; + export default serverBuild; +} diff --git a/apps/space/app/views/[anchor]/layout.tsx b/apps/space/app/views/[anchor]/layout.tsx deleted file mode 100644 index e2a38071c0f..00000000000 --- a/apps/space/app/views/[anchor]/layout.tsx +++ /dev/null @@ -1,65 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -import useSWR from "swr"; -// components -import { LogoSpinner } from "@/components/common/logo-spinner"; -import { PoweredBy } from "@/components/common/powered-by"; -import { SomethingWentWrongError } from "@/components/issues/issue-layouts/error"; -// hooks -import { usePublish, usePublishList } from "@/hooks/store/publish"; -// Plane web -import { ViewNavbarRoot } from "@/plane-web/components/navbar"; -import { useView } from "@/plane-web/hooks/store"; - -type Props = { - children: React.ReactNode; - params: { - anchor: string; - }; -}; - -const ViewsLayout = observer((props: Props) => { - const { children, params } = props; - // params - const { anchor } = params; - // store hooks - const { fetchPublishSettings } = usePublishList(); - const { viewData, fetchViewDetails } = useView(); - const publishSettings = usePublish(anchor); - - // fetch publish settings && view details - const { error } = useSWR( - anchor ? `PUBLISHED_VIEW_SETTINGS_${anchor}` : null, - anchor - ? async () => { - const promises = []; - promises.push(fetchPublishSettings(anchor)); - promises.push(fetchViewDetails(anchor)); - await Promise.all(promises); - } - : null - ); - - if (error) return ; - - if (!publishSettings || !viewData) { - return ( -
- -
- ); - } - - return ( -
-
- -
-
{children}
- -
- ); -}); - -export default ViewsLayout; diff --git a/apps/space/app/views/[anchor]/page.tsx b/apps/space/app/views/[anchor]/page.tsx deleted file mode 100644 index 5c877c89a3b..00000000000 --- a/apps/space/app/views/[anchor]/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -"use client"; - -import { observer } from "mobx-react"; -import { useSearchParams } from "next/navigation"; -// components -import { PoweredBy } from "@/components/common/powered-by"; -// hooks -import { usePublish } from "@/hooks/store/publish"; -// plane-web -import { ViewLayoutsRoot } from "@/plane-web/components/issue-layouts/root"; - -type Props = { - params: { - anchor: string; - }; -}; - -const ViewsPage = observer((props: Props) => { - const { params } = props; - const { anchor } = params; - // params - const searchParams = useSearchParams(); - const peekId = searchParams.get("peekId") || undefined; - - const publishSettings = usePublish(anchor); - - if (!publishSettings) return null; - - return ( - <> - - - - ); -}); - -export default ViewsPage; diff --git a/apps/space/core/components/account/auth-forms/auth-banner.tsx b/apps/space/core/components/account/auth-forms/auth-banner.tsx index feda960e715..3006beacfae 100644 --- a/apps/space/core/components/account/auth-forms/auth-banner.tsx +++ b/apps/space/core/components/account/auth-forms/auth-banner.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { Info } from "lucide-react"; import { CloseIcon } from "@plane/propel/icons"; // helpers @@ -11,7 +10,7 @@ type TAuthBanner = { handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void; }; -export const AuthBanner: FC = (props) => { +export const AuthBanner: React.FC = (props) => { const { bannerData, handleBannerData } = props; if (!bannerData) return <>; diff --git a/apps/space/core/components/account/auth-forms/auth-header.tsx b/apps/space/core/components/account/auth-forms/auth-header.tsx index 7996feedfa2..8e8aa196cb7 100644 --- a/apps/space/core/components/account/auth-forms/auth-header.tsx +++ b/apps/space/core/components/account/auth-forms/auth-header.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; // helpers import { EAuthModes } from "@/types/auth"; @@ -28,7 +27,7 @@ const Titles: TAuthHeaderDetails = { }, }; -export const AuthHeader: FC = (props) => { +export const AuthHeader: React.FC = (props) => { const { authMode } = props; const getHeaderSubHeader = (mode: EAuthModes | null): TAuthHeaderContent => { diff --git a/apps/space/core/components/account/auth-forms/auth-root.tsx b/apps/space/core/components/account/auth-forms/auth-root.tsx index 5a54b190614..52f0cb6f1ee 100644 --- a/apps/space/core/components/account/auth-forms/auth-root.tsx +++ b/apps/space/core/components/account/auth-forms/auth-root.tsx @@ -1,7 +1,6 @@ "use client"; -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; import { useSearchParams } from "next/navigation"; @@ -11,7 +10,12 @@ import { API_BASE_URL } from "@plane/constants"; import { SitesAuthService } from "@plane/services"; import type { IEmailCheckData } from "@plane/types"; import { OAuthOptions } from "@plane/ui"; -// components +// assets +import GiteaLogo from "@/app/assets/logos/gitea-logo.svg?url"; +import GithubLightLogo from "@/app/assets/logos/github-black.png?url"; +import GithubDarkLogo from "@/app/assets/logos/github-dark.svg?url"; +import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url"; +import GoogleLogo from "@/app/assets/logos/google-logo.svg?url"; // helpers import type { TAuthErrorInfo } from "@/helpers/authentication.helper"; import { EErrorAlertType, authErrorHandler, EAuthenticationErrorCodes } from "@/helpers/authentication.helper"; @@ -19,12 +23,6 @@ import { EErrorAlertType, authErrorHandler, EAuthenticationErrorCodes } from "@/ import { useInstance } from "@/hooks/store/use-instance"; // types import { EAuthModes, EAuthSteps } from "@/types/auth"; -// assets -import GithubLightLogo from "/public/logos/github-black.png"; -import GithubDarkLogo from "/public/logos/github-dark.svg"; -import GitlabLogo from "/public/logos/gitlab-logo.svg"; -import GoogleLogo from "/public/logos/google-logo.svg"; -import GiteaLogo from "/public/logos/gitea-logo.svg"; // local imports import { TermsAndConditions } from "../terms-and-conditions"; import { AuthBanner } from "./auth-banner"; @@ -35,7 +33,7 @@ import { AuthUniqueCodeForm } from "./unique-code"; const authService = new SitesAuthService(); -export const AuthRoot: FC = observer(() => { +export const AuthRoot: React.FC = observer(() => { // router params const searchParams = useSearchParams(); const emailParam = searchParams.get("email") || undefined; diff --git a/apps/space/core/components/account/auth-forms/email.tsx b/apps/space/core/components/account/auth-forms/email.tsx index 7abaef6f9ce..8b0fa712af2 100644 --- a/apps/space/core/components/account/auth-forms/email.tsx +++ b/apps/space/core/components/account/auth-forms/email.tsx @@ -1,6 +1,6 @@ "use client"; -import type { FC, FormEvent } from "react"; +import type { FormEvent } from "react"; import { useMemo, useRef, useState } from "react"; import { observer } from "mobx-react"; // icons @@ -19,7 +19,7 @@ type TAuthEmailForm = { onSubmit: (data: IEmailCheckData) => Promise; }; -export const AuthEmailForm: FC = observer((props) => { +export const AuthEmailForm: React.FC = observer((props) => { const { onSubmit, defaultEmail } = props; // states const [isSubmitting, setIsSubmitting] = useState(false); diff --git a/apps/space/core/components/account/terms-and-conditions.tsx b/apps/space/core/components/account/terms-and-conditions.tsx index 09611d92676..bd5a3ed2ead 100644 --- a/apps/space/core/components/account/terms-and-conditions.tsx +++ b/apps/space/core/components/account/terms-and-conditions.tsx @@ -1,14 +1,12 @@ "use client"; -import type { FC } from "react"; -import React from "react"; import Link from "next/link"; type Props = { isSignUp?: boolean; }; -export const TermsAndConditions: FC = (props) => { +export const TermsAndConditions: React.FC = (props) => { const { isSignUp = false } = props; return ( diff --git a/apps/space/core/components/account/user-logged-in.tsx b/apps/space/core/components/account/user-logged-in.tsx index 51175c16aec..de8118f2e53 100644 --- a/apps/space/core/components/account/user-logged-in.tsx +++ b/apps/space/core/components/account/user-logged-in.tsx @@ -3,13 +3,13 @@ import { observer } from "mobx-react"; import Image from "next/image"; import { PlaneLockup } from "@plane/propel/icons"; +// assets +import UserLoggedInImage from "@/app/assets/user-logged-in.svg?url"; // components import { PoweredBy } from "@/components/common/powered-by"; import { UserAvatar } from "@/components/issues/navbar/user-avatar"; // hooks import { useUser } from "@/hooks/store/use-user"; -// assets -import UserLoggedInImage from "@/public/user-logged-in.svg"; export const UserLoggedIn = observer(() => { // store hooks diff --git a/apps/space/core/components/common/logo-spinner.tsx b/apps/space/core/components/common/logo-spinner.tsx index 7b6a8e8ff9b..e7dae9ab574 100644 --- a/apps/space/core/components/common/logo-spinner.tsx +++ b/apps/space/core/components/common/logo-spinner.tsx @@ -2,8 +2,8 @@ import Image from "next/image"; import { useTheme } from "next-themes"; // assets -import LogoSpinnerDark from "@/public/images/logo-spinner-dark.gif"; -import LogoSpinnerLight from "@/public/images/logo-spinner-light.gif"; +import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url"; +import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url"; export const LogoSpinner = () => { const { resolvedTheme } = useTheme(); diff --git a/apps/space/core/components/common/powered-by.tsx b/apps/space/core/components/common/powered-by.tsx index 653c150f9a5..57734feb671 100644 --- a/apps/space/core/components/common/powered-by.tsx +++ b/apps/space/core/components/common/powered-by.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { WEBSITE_URL } from "@plane/constants"; // assets import { PlaneLogo } from "@plane/propel/icons"; @@ -9,7 +8,7 @@ type TPoweredBy = { disabled?: boolean; }; -export const PoweredBy: FC = (props) => { +export const PoweredBy: React.FC = (props) => { // props const { disabled = false } = props; diff --git a/apps/space/core/components/instance/instance-failure-view.tsx b/apps/space/core/components/instance/instance-failure-view.tsx index b1190285fec..a574f391d8e 100644 --- a/apps/space/core/components/instance/instance-failure-view.tsx +++ b/apps/space/core/components/instance/instance-failure-view.tsx @@ -1,14 +1,13 @@ "use client"; -import type { FC } from "react"; import Image from "next/image"; import { useTheme } from "next-themes"; import { Button } from "@plane/propel/button"; // assets -import InstanceFailureDarkImage from "public/instance/instance-failure-dark.svg"; -import InstanceFailureImage from "public/instance/instance-failure.svg"; +import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url"; +import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url"; -export const InstanceFailureView: FC = () => { +export const InstanceFailureView: React.FC = () => { const { resolvedTheme } = useTheme(); const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage; diff --git a/apps/space/core/components/issues/filters/applied-filters/root.tsx b/apps/space/core/components/issues/filters/applied-filters/root.tsx index f67749f9959..a266e8de9b6 100644 --- a/apps/space/core/components/issues/filters/applied-filters/root.tsx +++ b/apps/space/core/components/issues/filters/applied-filters/root.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useCallback } from "react"; import { cloneDeep } from "lodash-es"; import { observer } from "mobx-react"; @@ -16,7 +15,7 @@ type TIssueAppliedFilters = { anchor: string; }; -export const IssueAppliedFilters: FC = observer((props) => { +export const IssueAppliedFilters: React.FC = observer((props) => { const { anchor } = props; // router const router = useRouter(); diff --git a/apps/space/core/components/issues/filters/root.tsx b/apps/space/core/components/issues/filters/root.tsx index 8899a3378bc..6b21171c706 100644 --- a/apps/space/core/components/issues/filters/root.tsx +++ b/apps/space/core/components/issues/filters/root.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useCallback } from "react"; import { cloneDeep } from "lodash-es"; import { observer } from "mobx-react"; @@ -21,7 +20,7 @@ type IssueFiltersDropdownProps = { anchor: string; }; -export const IssueFiltersDropdown: FC = observer((props) => { +export const IssueFiltersDropdown: React.FC = observer((props) => { const { anchor } = props; // router const router = useRouter(); diff --git a/apps/space/core/components/issues/issue-layouts/error.tsx b/apps/space/core/components/issues/issue-layouts/error.tsx index 34789c21371..c52b7f34f95 100644 --- a/apps/space/core/components/issues/issue-layouts/error.tsx +++ b/apps/space/core/components/issues/issue-layouts/error.tsx @@ -1,6 +1,6 @@ import Image from "next/image"; // assets -import SomethingWentWrongImage from "public/something-went-wrong.svg"; +import SomethingWentWrongImage from "@/app/assets/something-went-wrong.svg?url"; export const SomethingWentWrongError = () => (
diff --git a/apps/space/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/apps/space/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index 5f56b9c2d19..a529afc7707 100644 --- a/apps/space/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/apps/space/core/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -1,7 +1,5 @@ "use client"; -import type { FC } from "react"; -import React from "react"; import { observer } from "mobx-react"; import { Circle } from "lucide-react"; // types @@ -14,7 +12,7 @@ interface IHeaderGroupByCard { count: number; } -export const HeaderGroupByCard: FC = observer((props) => { +export const HeaderGroupByCard: React.FC = observer((props) => { const { icon, title, count } = props; return ( diff --git a/apps/space/core/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx b/apps/space/core/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx index e656ee2540c..6e5654c9326 100644 --- a/apps/space/core/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx +++ b/apps/space/core/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx @@ -1,5 +1,3 @@ -import type { FC } from "react"; -import React from "react"; import { observer } from "mobx-react"; import { Circle } from "lucide-react"; import { ChevronDownIcon, ChevronUpIcon } from "@plane/propel/icons"; @@ -13,7 +11,7 @@ interface IHeaderSubGroupByCard { toggleExpanded: () => void; } -export const HeaderSubGroupByCard: FC = observer((props) => { +export const HeaderSubGroupByCard: React.FC = observer((props) => { const { icon, title, count, isExpanded, toggleExpanded } = props; return (
; } -export const IssueBlocksList: FC = (props) => { +export const IssueBlocksList: React.FC = (props) => { const { issueIds = [], groupId, displayProperties } = props; return ( diff --git a/apps/space/core/components/issues/issue-layouts/root.tsx b/apps/space/core/components/issues/issue-layouts/root.tsx index 3f9ee8d6af8..bbe8186693a 100644 --- a/apps/space/core/components/issues/issue-layouts/root.tsx +++ b/apps/space/core/components/issues/issue-layouts/root.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useEffect } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; @@ -23,7 +22,7 @@ type Props = { publishSettings: PublishStore; }; -export const IssuesLayoutsRoot: FC = observer((props) => { +export const IssuesLayoutsRoot: React.FC = observer((props) => { const { peekId, publishSettings } = props; // store hooks const { getIssueFilters } = useIssueFilter(); diff --git a/apps/space/core/components/issues/issue-layouts/with-display-properties-HOC.tsx b/apps/space/core/components/issues/issue-layouts/with-display-properties-HOC.tsx index 159b92a4d19..6b63d2bd5cd 100644 --- a/apps/space/core/components/issues/issue-layouts/with-display-properties-HOC.tsx +++ b/apps/space/core/components/issues/issue-layouts/with-display-properties-HOC.tsx @@ -1,4 +1,3 @@ -import type { ReactNode } from "react"; import { observer } from "mobx-react"; // plane imports import type { IIssueDisplayProperties } from "@plane/types"; @@ -7,7 +6,7 @@ interface IWithDisplayPropertiesHOC { displayProperties: IIssueDisplayProperties; shouldRenderProperty?: (displayProperties: IIssueDisplayProperties) => boolean; displayPropertyKey: keyof IIssueDisplayProperties | (keyof IIssueDisplayProperties)[]; - children: ReactNode; + children: React.ReactNode; } export const WithDisplayPropertiesHOC = observer( diff --git a/apps/space/core/components/issues/navbar/controls.tsx b/apps/space/core/components/issues/navbar/controls.tsx index 0616d8b5d2f..7913f479b54 100644 --- a/apps/space/core/components/issues/navbar/controls.tsx +++ b/apps/space/core/components/issues/navbar/controls.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { useEffect } from "react"; import { observer } from "mobx-react"; import { useRouter, useSearchParams } from "next/navigation"; @@ -25,7 +24,7 @@ export type NavbarControlsProps = { publishSettings: PublishStore; }; -export const NavbarControls: FC = observer((props) => { +export const NavbarControls: React.FC = observer((props) => { // props const { publishSettings } = props; // router diff --git a/apps/space/core/components/issues/navbar/layout-selection.tsx b/apps/space/core/components/issues/navbar/layout-selection.tsx index 8c3c2d04280..d6c196f54fd 100644 --- a/apps/space/core/components/issues/navbar/layout-selection.tsx +++ b/apps/space/core/components/issues/navbar/layout-selection.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { observer } from "mobx-react"; import { useRouter, useSearchParams } from "next/navigation"; // ui @@ -20,7 +19,7 @@ type Props = { anchor: string; }; -export const IssuesLayoutSelection: FC = observer((props) => { +export const IssuesLayoutSelection: React.FC = observer((props) => { const { anchor } = props; // hooks const { t } = useTranslation(); diff --git a/apps/space/core/components/issues/navbar/root.tsx b/apps/space/core/components/issues/navbar/root.tsx index 20f407e9dc3..d6e6bf08c8d 100644 --- a/apps/space/core/components/issues/navbar/root.tsx +++ b/apps/space/core/components/issues/navbar/root.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { observer } from "mobx-react"; import { ProjectIcon } from "@plane/propel/icons"; // components @@ -14,7 +13,7 @@ type Props = { publishSettings: PublishStore; }; -export const IssuesNavbarRoot: FC = observer((props) => { +export const IssuesNavbarRoot: React.FC = observer((props) => { const { publishSettings } = props; // hooks const { project_details } = publishSettings; diff --git a/apps/space/core/components/issues/navbar/user-avatar.tsx b/apps/space/core/components/issues/navbar/user-avatar.tsx index b5538cf707c..6b4d8cf0bd5 100644 --- a/apps/space/core/components/issues/navbar/user-avatar.tsx +++ b/apps/space/core/components/issues/navbar/user-avatar.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { Fragment, useEffect, useState } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; @@ -21,7 +20,7 @@ import { useUser } from "@/hooks/store/use-user"; const authService = new AuthService(); -export const UserAvatar: FC = observer(() => { +export const UserAvatar: React.FC = observer(() => { const pathName = usePathname(); const searchParams = useSearchParams(); // query params diff --git a/apps/space/core/components/issues/peek-overview/layout.tsx b/apps/space/core/components/issues/peek-overview/layout.tsx index 817700bfab2..a17e6d853c4 100644 --- a/apps/space/core/components/issues/peek-overview/layout.tsx +++ b/apps/space/core/components/issues/peek-overview/layout.tsx @@ -1,6 +1,5 @@ "use client"; -import type { FC } from "react"; import { Fragment, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useRouter, useSearchParams } from "next/navigation"; @@ -17,7 +16,7 @@ type TIssuePeekOverview = { handlePeekClose?: () => void; }; -export const IssuePeekOverview: FC = observer((props) => { +export const IssuePeekOverview: React.FC = observer((props) => { const { anchor, peekId, handlePeekClose } = props; const router = useRouter(); const searchParams = useSearchParams(); diff --git a/apps/space/core/components/ui/not-found.tsx b/apps/space/core/components/ui/not-found.tsx index 89fbca66738..abd46c59724 100644 --- a/apps/space/core/components/ui/not-found.tsx +++ b/apps/space/core/components/ui/not-found.tsx @@ -1,16 +1,15 @@ "use client"; -import React from "react"; import Image from "next/image"; // images -import Image404 from "@/public/404.svg"; +import Image404 from "@/app/assets/404.svg?url"; export const PageNotFound = () => (
- 404- Page not found + 404- Page not found

Oops! Something went wrong.

diff --git a/apps/space/core/lib/b-progress/AppProgressBar.tsx b/apps/space/core/lib/b-progress/AppProgressBar.tsx new file mode 100644 index 00000000000..7596e359a7b --- /dev/null +++ b/apps/space/core/lib/b-progress/AppProgressBar.tsx @@ -0,0 +1,125 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import { BProgress } from "@bprogress/core"; +import { useNavigation } from "react-router"; +import "@bprogress/core/css"; + +/** + * Progress bar configuration options + */ +interface ProgressConfig { + /** Whether to show the loading spinner */ + showSpinner: boolean; + /** Minimum progress percentage (0-1) */ + minimum: number; + /** Animation speed in milliseconds */ + speed: number; + /** Auto-increment speed in milliseconds */ + trickleSpeed: number; + /** CSS easing function */ + easing: string; + /** Enable auto-increment */ + trickle: boolean; + /** Delay before showing progress bar in milliseconds */ + delay: number; +} + +/** + * Configuration for the progress bar + */ +const PROGRESS_CONFIG: Readonly = { + showSpinner: false, + minimum: 0.1, + speed: 400, + trickleSpeed: 800, + easing: "ease", + trickle: true, + delay: 0, +} as const; + +/** + * Navigation Progress Bar Component + * + * Automatically displays a progress bar at the top of the page during React Router navigation. + * Integrates with React Router's useNavigation hook to monitor route changes. + * + * @returns null - This component doesn't render any visible elements + * + * @example + * ```tsx + * function App() { + * return ( + * <> + * + * + * + * ); + * } + * ``` + */ +export function AppProgressBar(): null { + const navigation = useNavigation(); + const timerRef = useRef | null>(null); + const startedRef = useRef(false); + + // Initialize BProgress once on mount + useEffect(() => { + // Configure BProgress with our settings + BProgress.configure({ + showSpinner: PROGRESS_CONFIG.showSpinner, + minimum: PROGRESS_CONFIG.minimum, + speed: PROGRESS_CONFIG.speed, + trickleSpeed: PROGRESS_CONFIG.trickleSpeed, + easing: PROGRESS_CONFIG.easing, + trickle: PROGRESS_CONFIG.trickle, + }); + + // Render the progress bar element in the DOM + BProgress.render(true); + + // Cleanup on unmount + return () => { + if (BProgress.isStarted()) { + BProgress.done(); + } + }; + }, []); + + // Handle navigation state changes + useEffect(() => { + if (navigation.state === "idle") { + // Navigation complete - clear any pending timer + if (timerRef.current !== null) { + clearTimeout(timerRef.current); + timerRef.current = null; + } + + // Complete progress if it was started + if (startedRef.current) { + BProgress.done(); + startedRef.current = false; + } + } else { + // Navigation in progress (loading or submitting) + // Only start if not already started and no timer pending + if (timerRef.current === null && !startedRef.current) { + timerRef.current = setTimeout((): void => { + if (!BProgress.isStarted()) { + BProgress.start(); + startedRef.current = true; + } + timerRef.current = null; + }, PROGRESS_CONFIG.delay); + } + } + + return () => { + if (timerRef.current !== null) { + clearTimeout(timerRef.current); + } + }; + }, [navigation.state]); + + return null; +} diff --git a/apps/space/core/lib/b-progress/index.tsx b/apps/space/core/lib/b-progress/index.tsx new file mode 100644 index 00000000000..7b531da2b25 --- /dev/null +++ b/apps/space/core/lib/b-progress/index.tsx @@ -0,0 +1 @@ +export * from "./AppProgressBar"; diff --git a/apps/space/core/lib/instance-provider.tsx b/apps/space/core/lib/instance-provider.tsx index 8ea98808610..2b005c2ae75 100644 --- a/apps/space/core/lib/instance-provider.tsx +++ b/apps/space/core/lib/instance-provider.tsx @@ -1,25 +1,25 @@ "use client"; -import type { ReactNode } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useTheme } from "next-themes"; import useSWR from "swr"; +// plane imports import { SPACE_BASE_PATH } from "@plane/constants"; +// assets +import PlaneBackgroundPatternDark from "@/app/assets/auth/background-pattern-dark.svg?url"; +import PlaneBackgroundPattern from "@/app/assets/auth/background-pattern.svg?url"; +import BlackHorizontalLogo from "@/app/assets/plane-logos/black-horizontal-with-blue-logo.png?url"; +import WhiteHorizontalLogo from "@/app/assets/plane-logos/white-horizontal-with-blue-logo.png?url"; // components import { LogoSpinner } from "@/components/common/logo-spinner"; import { InstanceFailureView } from "@/components/instance/instance-failure-view"; // hooks import { useInstance } from "@/hooks/store/use-instance"; import { useUser } from "@/hooks/store/use-user"; -// assets -import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg"; -import PlaneBackgroundPattern from "@/public/auth/background-pattern.svg"; -import BlackHorizontalLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; -import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; -export const InstanceProvider = observer(({ children }: { children: ReactNode }) => { +export const InstanceProvider = observer(({ children }: { children: React.ReactNode }) => { const { fetchInstanceInfo, instance, error } = useInstance(); const { fetchCurrentUser } = useUser(); const { resolvedTheme } = useTheme(); diff --git a/apps/space/core/lib/store-provider.tsx b/apps/space/core/lib/store-provider.tsx index b017f90c49c..cefb45c71e6 100644 --- a/apps/space/core/lib/store-provider.tsx +++ b/apps/space/core/lib/store-provider.tsx @@ -1,6 +1,5 @@ "use client"; -import type { ReactNode } from "react"; import { createContext } from "react"; // plane web store import { RootStore } from "@/plane-web/store/root.store"; @@ -19,7 +18,7 @@ function initializeStore() { } export type StoreProviderProps = { - children: ReactNode; + children: React.ReactNode; // eslint-disable-next-line @typescript-eslint/no-explicit-any initialState?: any; }; diff --git a/apps/space/core/lib/toast-provider.tsx b/apps/space/core/lib/toast-provider.tsx index e76c7e01e54..22c2d7ecc21 100644 --- a/apps/space/core/lib/toast-provider.tsx +++ b/apps/space/core/lib/toast-provider.tsx @@ -1,12 +1,11 @@ "use client"; -import type { ReactNode } from "react"; import { useTheme } from "next-themes"; // plane imports import { Toast } from "@plane/propel/toast"; import { resolveGeneralTheme } from "@plane/utils"; -export const ToastProvider = ({ children }: { children: ReactNode }) => { +export const ToastProvider = ({ children }: { children: React.ReactNode }) => { // themes const { resolvedTheme } = useTheme(); diff --git a/apps/space/helpers/authentication.helper.tsx b/apps/space/helpers/authentication.helper.tsx index 8c8f09c5471..b4878b12c5c 100644 --- a/apps/space/helpers/authentication.helper.tsx +++ b/apps/space/helpers/authentication.helper.tsx @@ -1,4 +1,3 @@ -import type { ReactNode } from "react"; import Link from "next/link"; // helpers import { SUPPORT_EMAIL } from "./common.helper"; @@ -83,11 +82,11 @@ export type TAuthErrorInfo = { type: EErrorAlertType; code: EAuthenticationErrorCodes; title: string; - message: ReactNode; + message: React.ReactNode; }; const errorCodeMessages: { - [key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode }; + [key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => React.ReactNode }; } = { // global [EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: { diff --git a/apps/space/helpers/common.helper.ts b/apps/space/helpers/common.helper.ts index cbb90199e96..787a64f546d 100644 --- a/apps/space/helpers/common.helper.ts +++ b/apps/space/helpers/common.helper.ts @@ -1,10 +1,4 @@ -import { clsx } from "clsx"; -import type { ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - export const SUPPORT_EMAIL = process.env.NEXT_PUBLIC_SUPPORT_EMAIL || ""; -export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs)); - export const resolveGeneralTheme = (resolvedTheme: string | undefined) => resolvedTheme?.includes("light") ? "light" : resolvedTheme?.includes("dark") ? "dark" : "system"; diff --git a/apps/space/middleware.js b/apps/space/middleware.js new file mode 100644 index 00000000000..f4e89a9451f --- /dev/null +++ b/apps/space/middleware.js @@ -0,0 +1,13 @@ +import { next } from "@vercel/edge"; + +export default function middleware() { + return next({ + headers: { + "Referrer-Policy": "origin-when-cross-origin", + "X-Frame-Options": "SAMEORIGIN", + "X-Content-Type-Options": "nosniff", + "X-DNS-Prefetch-Control": "on", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + }, + }); +} diff --git a/apps/space/next-env.d.ts b/apps/space/next-env.d.ts deleted file mode 100644 index 40c3d68096c..00000000000 --- a/apps/space/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/space/next.config.js b/apps/space/next.config.js deleted file mode 100644 index a736f4f6452..00000000000 --- a/apps/space/next.config.js +++ /dev/null @@ -1,43 +0,0 @@ -/** @type {import('next').NextConfig} */ - -const nextConfig = { - trailingSlash: true, - output: "standalone", - basePath: process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "", - reactStrictMode: false, - swcMinify: true, - async headers() { - return [ - { - source: "/", - headers: [{ key: "X-Frame-Options", value: "SAMEORIGIN" }], // clickjacking protection - }, - ]; - }, - images: { - remotePatterns: [ - { - protocol: "https", - hostname: "**", - }, - ], - unoptimized: true, - }, - experimental: { - optimizePackageImports: [ - "@plane/constants", - "@plane/editor", - "@plane/hooks", - "@plane/i18n", - "@plane/logger", - "@plane/propel", - "@plane/services", - "@plane/shared-state", - "@plane/types", - "@plane/ui", - "@plane/utils", - ], - }, -}; - -module.exports = nextConfig; diff --git a/apps/space/nginx/nginx.conf b/apps/space/nginx/nginx.conf new file mode 100644 index 00000000000..c0fe3730d10 --- /dev/null +++ b/apps/space/nginx/nginx.conf @@ -0,0 +1,30 @@ +worker_processes 4; + +events { + worker_connections 1024; +} + +http { + include mime.types; + + default_type application/octet-stream; + + 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/ /spaces/index.html; + } + } +} + diff --git a/apps/space/package.json b/apps/space/package.json index f582fb07f8f..a0e2ea74e19 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -3,22 +3,24 @@ "version": "1.1.0", "private": true, "license": "AGPL-3.0", + "type": "module", "scripts": { - "dev": "next dev -p 3002", - "build": "next build", - "start": "next start", - "clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist", + "dev": "cross-env NODE_ENV=development PORT=3002 node server.mjs", + "build": "react-router build", + "preview": "react-router build && cross-env NODE_ENV=production PORT=3002 node server.mjs", + "start": "serve -s build/client -l 3002", + "clean": "rm -rf .turbo && rm -rf .next && rm -rf .react-router && rm -rf node_modules && rm -rf dist && rm -rf build", "check:lint": "eslint . --max-warnings 28", - "check:types": "tsc --noEmit", + "check:types": "react-router typegen && tsc --noEmit", "check:format": "prettier --check \"**/*.{ts,tsx,md,json,css,scss}\"", "fix:lint": "eslint . --fix", "fix:format": "prettier --write \"**/*.{ts,tsx,md,json,css,scss}\"" }, "dependencies": { + "@bprogress/core": "catalog:", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@headlessui/react": "^1.7.13", - "@mui/material": "^5.14.1", "@plane/constants": "workspace:*", "@plane/editor": "workspace:*", "@plane/i18n": "workspace:*", @@ -28,38 +30,50 @@ "@plane/ui": "workspace:*", "@plane/utils": "workspace:*", "@popperjs/core": "^2.11.8", + "@react-router/express": "^7.9.3", + "@react-router/node": "^7.9.3", + "@vercel/edge": "1.2.2", "axios": "catalog:", "clsx": "^2.0.0", + "compression": "^1.8.1", + "cross-env": "^7.0.3", "date-fns": "^4.1.0", - "dotenv": "^16.3.1", + "dotenv": "^16.4.5", + "express": "^5.1.0", + "http-proxy-middleware": "^3.0.5", + "isbot": "^5.1.31", "lodash-es": "catalog:", - "lowlight": "^2.9.0", "lucide-react": "catalog:", "mobx": "catalog:", "mobx-react": "catalog:", "mobx-utils": "catalog:", - "next": "catalog:", + "morgan": "^1.10.1", "next-themes": "^0.2.1", - "nprogress": "^0.2.0", "react": "catalog:", "react-dom": "catalog:", "react-dropzone": "^14.2.3", "react-hook-form": "7.51.5", "react-popper": "^2.3.0", - "sharp": "catalog:", + "react-router": "^7.9.1", + "react-router-dom": "^7.9.1", + "serve": "14.2.5", "swr": "catalog:", - "tailwind-merge": "^2.0.0", "uuid": "catalog:" }, "devDependencies": { "@plane/eslint-config": "workspace:*", "@plane/tailwind-config": "workspace:*", "@plane/typescript-config": "workspace:*", + "@react-router/dev": "^7.9.1", + "@types/compression": "^1.8.1", + "@types/express": "4.17.23", "@types/lodash-es": "catalog:", + "@types/morgan": "^1.9.10", "@types/node": "catalog:", - "@types/nprogress": "^0.2.0", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vite": "7.1.7", + "vite-tsconfig-paths": "^5.1.4" } } diff --git a/apps/space/postcss.config.js b/apps/space/postcss.config.cjs similarity index 100% rename from apps/space/postcss.config.js rename to apps/space/postcss.config.cjs diff --git a/apps/space/react-router.config.ts b/apps/space/react-router.config.ts new file mode 100644 index 00000000000..b046a7a1212 --- /dev/null +++ b/apps/space/react-router.config.ts @@ -0,0 +1,8 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + appDirectory: "app", + basename: process.env.NEXT_PUBLIC_SPACE_BASE_PATH, + // Space runs as a client-side app; build a static client bundle only + ssr: false, +} satisfies Config; diff --git a/apps/space/server.mjs b/apps/space/server.mjs new file mode 100644 index 00000000000..3342bd0be9f --- /dev/null +++ b/apps/space/server.mjs @@ -0,0 +1,76 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import compression from "compression"; +import dotenv from "dotenv"; +import express from "express"; +import morgan from "morgan"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +dotenv.config({ path: path.resolve(__dirname, ".env") }); + +const BUILD_PATH = "./build/server/index.js"; +const DEVELOPMENT = process.env.NODE_ENV !== "production"; + +// Derive the port from NEXT_PUBLIC_SPACE_BASE_URL when available, otherwise +// default to http://localhost:3002 and fall back to PORT env if explicitly set. +const DEFAULT_BASE_URL = "http://localhost:3002"; +const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || DEFAULT_BASE_URL; +let parsedBaseUrl; +try { + parsedBaseUrl = new URL(SPACE_BASE_URL); +} catch { + parsedBaseUrl = new URL(DEFAULT_BASE_URL); +} + +const PORT = Number.parseInt(parsedBaseUrl.port, 10); + +async function start() { + const app = express(); + + app.use(compression()); + app.disable("x-powered-by"); + + if (DEVELOPMENT) { + console.log("Starting development server"); + + const vite = await import("vite").then((vite) => + vite.createServer({ + server: { middlewareMode: true }, + appType: "custom", + }) + ); + + app.use(vite.middlewares); + + app.use(async (req, res, next) => { + try { + const source = await vite.ssrLoadModule("./server/app.ts"); + return source.app(req, res, next); + } catch (error) { + if (error instanceof Error) { + vite.ssrFixStacktrace(error); + } + + next(error); + } + }); + } else { + console.log("Starting production server"); + + app.use("/assets", express.static("build/client/assets", { immutable: true, maxAge: "1y" })); + app.use(morgan("tiny")); + app.use(express.static("build/client", { maxAge: "1h" })); + app.use(await import(BUILD_PATH).then((mod) => mod.app)); + } + + app.listen(PORT, () => { + const origin = `${parsedBaseUrl.protocol}//${parsedBaseUrl.hostname}:${PORT}`; + console.log(`Server is running on ${origin}`); + }); +} + +start().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/apps/space/server/app.ts b/apps/space/server/app.ts new file mode 100644 index 00000000000..d21b5bec5be --- /dev/null +++ b/apps/space/server/app.ts @@ -0,0 +1,46 @@ +import "react-router"; +import { createRequestHandler } from "@react-router/express"; +import express from "express"; +import type { Express } from "express"; +import { createProxyMiddleware } from "http-proxy-middleware"; + +const NEXT_PUBLIC_API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL + ? process.env.NEXT_PUBLIC_API_BASE_URL.replace(/\/$/, "") + : "http://127.0.0.1:8000"; +const NEXT_PUBLIC_API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH + ? process.env.NEXT_PUBLIC_API_BASE_PATH.replace(/\/+$/, "") + : "/api"; +const NORMALIZED_API_BASE_PATH = NEXT_PUBLIC_API_BASE_PATH.startsWith("/") + ? NEXT_PUBLIC_API_BASE_PATH + : `/${NEXT_PUBLIC_API_BASE_PATH}`; +const NEXT_PUBLIC_SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH + ? process.env.NEXT_PUBLIC_SPACE_BASE_PATH.replace(/\/$/, "") + : "/"; + +export const app: Express = express(); + +// Ensure proxy-aware hostname/URL handling (e.g., X-Forwarded-Host/Proto) +// so generated URLs/redirects reflect the public host when behind Nginx. +// See related fix in Remix Express adapter. +app.set("trust proxy", true); + +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), + }) +); + +const router = express.Router(); + +router.use( + createRequestHandler({ + build: () => import("virtual:react-router/server-build"), + }) +); + +app.use(NEXT_PUBLIC_SPACE_BASE_PATH, router); diff --git a/apps/space/styles/globals.css b/apps/space/styles/globals.css index 5f2e91ed238..46fca340278 100644 --- a/apps/space/styles/globals.css +++ b/apps/space/styles/globals.css @@ -495,3 +495,30 @@ body { .scrollbar-lg::-webkit-scrollbar-thumb { border: 4px solid rgba(0, 0, 0, 0); } + +/* Progress Bar Styles */ +:root { + --bprogress-color: rgb(var(--color-primary-100)) !important; + --bprogress-height: 2.5px !important; +} + +.bprogress { + pointer-events: none; +} + +.bprogress .bar { + background: linear-gradient( + 90deg, + rgba(var(--color-primary-100), 0.8) 0%, + rgba(var(--color-primary-100), 1) 100% + ) !important; + will-change: width, opacity; +} + +.bprogress .peg { + display: block; + box-shadow: + 0 0 8px rgba(var(--color-primary-100), 0.6), + 0 0 4px rgba(var(--color-primary-100), 0.4) !important; + will-change: transform, opacity; +} diff --git a/apps/space/tailwind.config.js b/apps/space/tailwind.config.cjs similarity index 100% rename from apps/space/tailwind.config.js rename to apps/space/tailwind.config.cjs diff --git a/apps/space/tsconfig.json b/apps/space/tsconfig.json index a4f7c1dae88..fc26e2e4ab8 100644 --- a/apps/space/tsconfig.json +++ b/apps/space/tsconfig.json @@ -1,28 +1,18 @@ { - "extends": "@plane/typescript-config/nextjs.json", - "plugins": [ - { - "name": "next" - } - ], - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts", ".next/types/**/*.ts"], - "exclude": ["node_modules"], + "extends": "@plane/typescript-config/react-router.json", "compilerOptions": { "baseUrl": ".", - "jsx": "preserve", + "rootDirs": [".", "./.react-router/types"], + "types": ["node", "vite/client"], "paths": { + "@/app/*": ["app/*"], "@/*": ["core/*"], "@/helpers/*": ["helpers/*"], - "@/public/*": ["public/*"], "@/styles/*": ["styles/*"], "@/plane-web/*": ["ce/*"] }, - "plugins": [ - { - "name": "next" - } - ], - "strictNullChecks": true - } + }, + "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*", "additional.d.ts"], + "exclude": ["node_modules"] } diff --git a/apps/space/vite.config.ts b/apps/space/vite.config.ts new file mode 100644 index 00000000000..0ef1c5141d1 --- /dev/null +++ b/apps/space/vite.config.ts @@ -0,0 +1,59 @@ +import path from "node:path"; +import { reactRouter } from "@react-router/dev/vite"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { joinUrlPath } from "@plane/utils"; + +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", +]; + +const publicEnv = PUBLIC_ENV_KEYS.reduce>((acc, key) => { + acc[key] = process.env[key] ?? ""; + return acc; +}, {}); + +export default defineConfig(({ isSsrBuild }) => { + // Only produce an SSR bundle when explicitly enabled. + // For static deployments (default), we skip the server build entirely. + const enableSsrBuild = process.env.SPACE_ENABLE_SSR_BUILD === "true"; + const basePath = joinUrlPath(process.env.NEXT_PUBLIC_SPACE_BASE_PATH ?? "", "/") ?? "/"; + + return { + base: basePath, + define: { + "process.env": JSON.stringify(publicEnv), + }, + build: { + assetsInlineLimit: 0, + rollupOptions: + isSsrBuild && enableSsrBuild + ? { + input: path.resolve(__dirname, "server/app.ts"), + } + : undefined, + }, + plugins: [reactRouter(), tsconfigPaths({ projects: [path.resolve(__dirname, "tsconfig.json")] })], + resolve: { + alias: { + // Next.js compatibility shims used within space + "next/image": path.resolve(__dirname, "app/compat/next/image.tsx"), + "next/link": path.resolve(__dirname, "app/compat/next/link.tsx"), + "next/navigation": path.resolve(__dirname, "app/compat/next/navigation.ts"), + }, + dedupe: ["react", "react-dom"], + }, + // No SSR-specific overrides needed; alias resolves to ESM build + }; +}); diff --git a/apps/web/app/(all)/layout.tsx b/apps/web/app/(all)/layout.tsx index ee6c5750da9..72bf0c206b5 100644 --- a/apps/web/app/(all)/layout.tsx +++ b/apps/web/app/(all)/layout.tsx @@ -5,7 +5,7 @@ import { PreloadResources } from "./layout.preload"; // styles import "@/styles/power-k.css"; import "@/styles/emoji.css"; -import "@plane/propel/styles/react-day-picker"; +import "@plane/propel/styles/react-day-picker.css"; export const metadata: Metadata = { robots: { diff --git a/packages/ui/package.json b/packages/ui/package.json index 0f0ce349d4f..a82ba06467f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -41,9 +41,9 @@ "@headlessui/react": "^1.7.3", "@plane/constants": "workspace:*", "@plane/hooks": "workspace:*", + "@plane/propel": "workspace:*", "@plane/types": "workspace:*", "@plane/utils": "workspace:*", - "@plane/propel": "workspace:*", "@popperjs/core": "^2.11.8", "@radix-ui/react-scroll-area": "^1.2.3", "clsx": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb4d5653c3c..3727381be2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -379,6 +379,9 @@ importers: apps/space: dependencies: + '@bprogress/core': + specifier: 'catalog:' + version: 1.3.4 '@emotion/react': specifier: ^11.11.1 version: 11.14.0(@types/react@18.3.11)(react@18.3.1) @@ -388,9 +391,6 @@ importers: '@headlessui/react': specifier: ^1.7.13 version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/material': - specifier: ^5.14.1 - version: 5.17.1(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@plane/constants': specifier: workspace:* version: link:../../packages/constants @@ -418,24 +418,45 @@ importers: '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 + '@react-router/express': + specifier: ^7.9.3 + version: 7.9.4(express@5.1.0)(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3) + '@react-router/node': + specifier: ^7.9.3 + version: 7.9.4(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3) + '@vercel/edge': + specifier: 1.2.2 + version: 1.2.2 axios: specifier: 'catalog:' version: 1.12.0 clsx: specifier: ^2.0.0 version: 2.1.1 + compression: + specifier: ^1.8.1 + version: 1.8.1 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 date-fns: specifier: ^4.1.0 version: 4.1.0 dotenv: - specifier: ^16.3.1 + specifier: ^16.4.5 version: 16.6.1 + express: + specifier: ^5.1.0 + version: 5.1.0 + http-proxy-middleware: + specifier: ^3.0.5 + version: 3.0.5 + isbot: + specifier: ^5.1.31 + version: 5.1.31 lodash-es: specifier: 'catalog:' version: 4.17.21 - lowlight: - specifier: ^2.9.0 - version: 2.9.0 lucide-react: specifier: 'catalog:' version: 0.469.0(react@18.3.1) @@ -448,15 +469,12 @@ importers: mobx-utils: specifier: 'catalog:' version: 6.0.8(mobx@6.12.0) - next: - specifier: 'catalog:' - version: 14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + morgan: + specifier: ^1.10.1 + version: 1.10.1 next-themes: specifier: ^0.2.1 version: 0.2.1(next@14.2.32(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nprogress: - specifier: ^0.2.0 - version: 0.2.0 react: specifier: 'catalog:' version: 18.3.1 @@ -472,15 +490,18 @@ importers: react-popper: specifier: ^2.3.0 version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - sharp: - specifier: 0.33.5 - version: 0.33.5 + react-router: + specifier: ^7.9.1 + version: 7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router-dom: + specifier: ^7.9.1 + version: 7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + serve: + specifier: 14.2.5 + version: 14.2.5 swr: specifier: 'catalog:' version: 2.2.4(react@18.3.1) - tailwind-merge: - specifier: ^2.0.0 - version: 2.6.0 uuid: specifier: 'catalog:' version: 13.0.0 @@ -494,15 +515,24 @@ importers: '@plane/typescript-config': specifier: workspace:* version: link:../../packages/typescript-config + '@react-router/dev': + specifier: ^7.9.1 + version: 7.9.3(@types/node@22.12.0)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.43.1)(typescript@5.8.3)(vite@7.1.11(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(yaml@2.8.1) + '@types/compression': + specifier: ^1.8.1 + version: 1.8.1 + '@types/express': + specifier: 4.17.23 + version: 4.17.23 '@types/lodash-es': specifier: 'catalog:' version: 4.17.12 + '@types/morgan': + specifier: ^1.9.10 + version: 1.9.10 '@types/node': specifier: 'catalog:' version: 22.12.0 - '@types/nprogress': - specifier: ^0.2.0 - version: 0.2.3 '@types/react': specifier: 'catalog:' version: 18.3.11 @@ -512,6 +542,12 @@ importers: typescript: specifier: 5.8.3 version: 5.8.3 + vite: + specifier: 7.1.11 + version: 7.1.11(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.8.3)(vite@7.1.11(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)) apps/web: dependencies: @@ -2215,83 +2251,6 @@ packages: '@mjackson/node-fetch-server@0.2.0': resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} - '@mui/core-downloads-tracker@5.18.0': - resolution: {integrity: sha512-jbhwoQ1AY200PSSOrNXmrFCaSDSJWP7qk6urkTmIirvRXDROkqe+QwcLlUiw/PrREwsIF/vm3/dAXvjlMHF0RA==} - - '@mui/material@5.17.1': - resolution: {integrity: sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - - '@mui/private-theming@5.17.1': - resolution: {integrity: sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/styled-engine@5.18.0': - resolution: {integrity: sha512-BN/vKV/O6uaQh2z5rXV+MBlVrEkwoS/TK75rFQ2mjxA7+NBo8qtTAOA4UaM0XeJfn7kh2wZ+xQw2HAx0u+TiBg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - - '@mui/system@5.18.0': - resolution: {integrity: sha512-ojZGVcRWqWhu557cdO3pWHloIGJdzVtxs3rk0F9L+x55LsUjcMUVkEhiF7E4TMxZoF9MmIHGGs0ZX3FDLAf0Xw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - - '@mui/types@7.2.24': - resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/utils@5.17.1': - resolution: {integrity: sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -2872,6 +2831,27 @@ packages: '@react-pdf/types@2.9.0': resolution: {integrity: sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ==} + '@react-router/dev@7.9.3': + resolution: {integrity: sha512-oPaO+OpvCo/rNTJrRipHSp31/K4It19PE5A24x21FlYlemPTe3fbGX/kyC2+8au/abXbvzNHfRbuIBD/rfojmA==} + engines: {node: '>=20.0.0'} + hasBin: true + peerDependencies: + '@react-router/serve': ^7.9.3 + '@vitejs/plugin-rsc': '*' + react-router: ^7.9.3 + typescript: 5.8.3 + vite: 7.1.11 + wrangler: ^3.28.2 || ^4.0.0 + peerDependenciesMeta: + '@react-router/serve': + optional: true + '@vitejs/plugin-rsc': + optional: true + typescript: + optional: true + wrangler: + optional: true + '@react-router/dev@7.9.4': resolution: {integrity: sha512-bLs6DjKMJExT7Y57EBx25hkeGGUla3pURxvOn15IN8Mmaw2+euDtBUX9+OFrAPsAzD1xIj6+2HNLXlFH/LB86Q==} engines: {node: '>=20.0.0'} @@ -2904,6 +2884,16 @@ packages: typescript: optional: true + '@react-router/node@7.9.3': + resolution: {integrity: sha512-+OvWxPPUgouOshw85QlG0J6yFJM0GMCCpXqPj38IcveeFLlP7ppOAEkOi7RBFrDvg7vSUtCEBDnsbuDCvxUPJg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react-router: 7.9.3 + typescript: 5.8.3 + peerDependenciesMeta: + typescript: + optional: true + '@react-router/node@7.9.4': resolution: {integrity: sha512-sdeDNRaqAB71BR2hPlhcQbPbrXh8uGJUjLVc+NpRiPsQbv6B8UvIucN4IX9YGVJkw3UxVQBn2vPSwxACAck32Q==} engines: {node: '>=20.0.0'} @@ -3936,9 +3926,6 @@ packages: '@types/node@22.12.0': resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==} - '@types/nprogress@0.2.3': - resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} - '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -3965,11 +3952,6 @@ packages: '@types/react-dom@18.3.1': resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - '@types/react-transition-group@4.4.12': - resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} - peerDependencies: - '@types/react': '*' - '@types/react@18.3.11': resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} @@ -5561,9 +5543,6 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fault@2.0.1: - resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5679,10 +5658,6 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -5870,10 +5845,6 @@ packages: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - highlight.js@11.8.0: - resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==} - engines: {node: '>=12.0.0'} - hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -6388,9 +6359,6 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lowlight@2.9.0: - resolution: {integrity: sha512-OpcaUTCLmHuVuBcyNckKfH5B0oA4JUavb/M/8n9iAvanJYNQkrVm4pvyX0SUaqkBG4dnWHKt7p50B3ngAG2Rfw==} - lowlight@3.3.0: resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} @@ -6782,9 +6750,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - nprogress@0.2.0: - resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -7466,9 +7431,6 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.1.1: - resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==} - react-markdown@8.0.7: resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==} peerDependencies: @@ -8510,6 +8472,14 @@ packages: engines: {node: '>=8'} hasBin: true + valibot@0.41.0: + resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==} + peerDependencies: + typescript: 5.8.3 + peerDependenciesMeta: + typescript: + optional: true + valibot@1.1.0: resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} peerDependencies: @@ -9638,82 +9608,6 @@ snapshots: '@mjackson/node-fetch-server@0.2.0': {} - '@mui/core-downloads-tracker@5.18.0': {} - - '@mui/material@5.17.1(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/core-downloads-tracker': 5.18.0 - '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.11) - '@mui/utils': 5.17.1(@types/react@18.3.11)(react@18.3.1) - '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@18.3.11) - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 19.1.1 - react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.11)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1) - '@types/react': 18.3.11 - - '@mui/private-theming@5.17.1(@types/react@18.3.11)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/utils': 5.17.1(@types/react@18.3.11)(react@18.3.1) - prop-types: 15.8.1 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.11 - - '@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.10 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.3.1 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.11)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1) - - '@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/private-theming': 5.17.1(@types/react@18.3.11)(react@18.3.1) - '@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.11) - '@mui/utils': 5.17.1(@types/react@18.3.11)(react@18.3.1) - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.3.1 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.11)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.11)(react@18.3.1))(@types/react@18.3.11)(react@18.3.1) - '@types/react': 18.3.11 - - '@mui/types@7.2.24(@types/react@18.3.11)': - optionalDependencies: - '@types/react': 18.3.11 - - '@mui/utils@5.17.1(@types/react@18.3.11)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.10 - '@mui/types': 7.2.24(@types/react@18.3.11) - '@types/prop-types': 15.7.15 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.3.1 - react-is: 19.1.1 - optionalDependencies: - '@types/react': 18.3.11 - '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.5.0 @@ -10403,6 +10297,55 @@ snapshots: '@react-pdf/primitives': 4.1.1 '@react-pdf/stylesheet': 6.1.0 + '@react-router/dev@7.9.3(@types/node@22.12.0)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.43.1)(typescript@5.8.3)(vite@7.1.11(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(yaml@2.8.1)': + dependencies: + '@babel/core': 7.28.4 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.4) + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@npmcli/package-json': 4.0.1 + '@react-router/node': 7.9.3(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3) + '@remix-run/node-fetch-server': 0.9.0 + arg: 5.0.2 + babel-dead-code-elimination: 1.0.10 + chokidar: 3.6.0 + dedent: 1.7.0(babel-plugin-macros@3.1.0) + es-module-lexer: 1.7.0 + exit-hook: 2.2.1 + isbot: 5.1.31 + jsesc: 3.0.2 + lodash: 4.17.21 + pathe: 1.1.2 + picocolors: 1.1.1 + prettier: 3.6.2 + react-refresh: 0.14.2 + react-router: 7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + semver: 7.7.2 + tinyglobby: 0.2.15 + valibot: 0.41.0(typescript@5.8.3) + vite: 7.1.11(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - bluebird + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@react-router/dev@7.9.4(@types/node@22.12.0)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(react-router@7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.43.1)(typescript@5.8.3)(vite@7.1.11(@types/node@22.12.0)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))(yaml@2.8.1)': dependencies: '@babel/core': 7.28.4 @@ -10452,6 +10395,14 @@ snapshots: - tsx - yaml + '@react-router/express@7.9.4(express@5.1.0)(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3)': + dependencies: + '@react-router/node': 7.9.4(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3) + express: 5.1.0 + react-router: 7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + typescript: 5.8.3 + '@react-router/express@7.9.4(express@5.1.0)(react-router@7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3)': dependencies: '@react-router/node': 7.9.4(react-router@7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3) @@ -10460,6 +10411,20 @@ snapshots: optionalDependencies: typescript: 5.8.3 + '@react-router/node@7.9.3(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3)': + dependencies: + '@mjackson/node-fetch-server': 0.2.0 + react-router: 7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + typescript: 5.8.3 + + '@react-router/node@7.9.4(react-router@7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3)': + dependencies: + '@mjackson/node-fetch-server': 0.2.0 + react-router: 7.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + typescript: 5.8.3 + '@react-router/node@7.9.4(react-router@7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 @@ -11633,8 +11598,6 @@ snapshots: dependencies: undici-types: 6.20.0 - '@types/nprogress@0.2.3': {} - '@types/parse-json@4.0.2': {} '@types/pg-pool@2.0.6': @@ -11662,10 +11625,6 @@ snapshots: dependencies: '@types/react': 18.3.11 - '@types/react-transition-group@4.4.12(@types/react@18.3.11)': - dependencies: - '@types/react': 18.3.11 - '@types/react@18.3.11': dependencies: '@types/prop-types': 15.7.15 @@ -13525,10 +13484,6 @@ snapshots: dependencies: reusify: 1.1.0 - fault@2.0.1: - dependencies: - format: 0.2.2 - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -13677,8 +13632,6 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - format@0.2.2: {} - forwarded-parse@2.1.2: {} forwarded@0.2.0: {} @@ -13857,8 +13810,6 @@ snapshots: highlight.js@11.11.1: {} - highlight.js@11.8.0: {} - hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -14382,12 +14333,6 @@ snapshots: dependencies: tslib: 2.8.1 - lowlight@2.9.0: - dependencies: - '@types/hast': 2.3.10 - fault: 2.0.1 - highlight.js: 11.8.0 - lowlight@3.3.0: dependencies: '@types/hast': 3.0.4 @@ -14833,8 +14778,6 @@ snapshots: dependencies: path-key: 3.1.1 - nprogress@0.2.0: {} - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -15491,8 +15434,6 @@ snapshots: react-is@18.3.1: {} - react-is@19.1.1: {} - react-markdown@8.0.7(@types/react@18.3.11)(react@18.3.1): dependencies: '@types/hast': 2.3.10 @@ -16791,6 +16732,10 @@ snapshots: kleur: 4.1.5 sade: 1.8.1 + valibot@0.41.0(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + valibot@1.1.0(typescript@5.8.3): optionalDependencies: typescript: 5.8.3