diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx index 1fab5aa6334..c0cea1b9ff8 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx @@ -12,7 +12,7 @@ import emptyIssueDark from "@/app/assets/empty-state/search/issues-dark.webp?url import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp?url"; // components import { EmptyState } from "@/components/common/empty-state"; -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; // hooks import { useAppRouter } from "@/hooks/use-app-router"; // services @@ -66,7 +66,7 @@ export default function IssueDetailsPage({ loaderData }: Route.ComponentProps) { return (
- +
); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx index fc1f76aa5d4..f0caf31eed2 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx @@ -9,6 +9,7 @@ import { observer } from "mobx-react"; import Link from "next/link"; import useSWR from "swr"; // plane types +import { useTranslation } from "@plane/i18n"; import { getButtonStyling } from "@plane/propel/button"; import type { TSearchEntityRequestPayload, TWebhookConnectionQueryParams } from "@plane/types"; import { EFileAssetType } from "@plane/types"; @@ -16,7 +17,7 @@ import { EFileAssetType } from "@plane/types"; // plane utils import { cn } from "@plane/utils"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { PageHead } from "@/components/core/page-title"; import { IssuePeekOverview } from "@/components/issues/peek-overview"; import type { TPageRootConfig, TPageRootHandlers } from "@/components/pages/editor/page-root"; @@ -43,6 +44,8 @@ function PageDetailsPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); const { workspaceSlug, projectId, pageId } = params; + // translation + const { t } = useTranslation(); // store hooks const { createPage, fetchPageDetails } = usePageStore(storeType); const page = usePage({ @@ -154,7 +157,7 @@ function PageDetailsPage({ params }: Route.ComponentProps) { if ((!page || !id) && !pageDetailsError) return (
- +
); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx index 5cb9d3c17eb..1524aa4f546 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx @@ -8,11 +8,12 @@ import { useState } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { IWebhook } from "@plane/types"; // ui // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { PageHead } from "@/components/core/page-title"; import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "@/components/web-hooks"; @@ -29,6 +30,8 @@ function WebhookDetailsPage({ params }: Route.ComponentProps) { const [deleteWebhookModal, setDeleteWebhookModal] = useState(false); // router const { workspaceSlug, webhookId } = params; + // translation + const { t } = useTranslation(); // mobx store const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook(); const { currentWorkspace } = useWorkspace(); @@ -90,7 +93,7 @@ function WebhookDetailsPage({ params }: Route.ComponentProps) { if (!currentWebhook) return (
- +
); diff --git a/apps/web/app/(all)/onboarding/page.tsx b/apps/web/app/(all)/onboarding/page.tsx index 86ef1b1f724..30bec6eaee1 100644 --- a/apps/web/app/(all)/onboarding/page.tsx +++ b/apps/web/app/(all)/onboarding/page.tsx @@ -8,10 +8,11 @@ import { observer } from "mobx-react"; import useSWR from "swr"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { OnboardingRoot } from "@/components/onboarding"; // constants import { USER_WORKSPACES_LIST } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // helpers import { EPageTypes } from "@/helpers/authentication.helper"; // hooks @@ -28,6 +29,8 @@ function OnboardingPage() { // store hooks const { data: user } = useUser(); const { fetchWorkspaces } = useWorkspace(); + // translation + const { t } = useTranslation(); // fetching workspaces list useSWR(USER_WORKSPACES_LIST, () => { @@ -53,7 +56,7 @@ function OnboardingPage() { ) : (
- +
)} diff --git a/apps/web/app/(all)/settings/profile/[profileTabId]/page.tsx b/apps/web/app/(all)/settings/profile/[profileTabId]/page.tsx index d7df27ccbcd..2003dfb720d 100644 --- a/apps/web/app/(all)/settings/profile/[profileTabId]/page.tsx +++ b/apps/web/app/(all)/settings/profile/[profileTabId]/page.tsx @@ -10,7 +10,7 @@ import { PROFILE_SETTINGS_TABS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { TProfileSettingsTabs } from "@plane/types"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { PageHead } from "@/components/core/page-title"; import { ProfileSettingsContent } from "@/components/settings/profile/content"; import { ProfileSettingsSidebarRoot } from "@/components/settings/profile/sidebar"; @@ -34,7 +34,7 @@ function ProfileSettingsPage(props: Route.ComponentProps) { if (!currentUser || !isAValidTab) return (
- +
); diff --git a/apps/web/app/(all)/workspace-invitations/page.tsx b/apps/web/app/(all)/workspace-invitations/page.tsx index d7e580e86fc..df540e6a081 100644 --- a/apps/web/app/(all)/workspace-invitations/page.tsx +++ b/apps/web/app/(all)/workspace-invitations/page.tsx @@ -16,10 +16,11 @@ import { UserOutline, } from "@makeplane/propel/icons"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space"; // constants import { WORKSPACE_INVITATION } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // helpers import { EPageTypes } from "@/helpers/authentication.helper"; // hooks @@ -41,6 +42,8 @@ function WorkspaceInvitationPage() { const invitation_id = searchParams.get("invitation_id"); const slug = searchParams.get("slug"); const token = searchParams.get("token"); + // translation + const { t } = useTranslation(); // store hooks const { data: currentUser } = useUser(); @@ -127,7 +130,7 @@ function WorkspaceInvitationPage() { ) ) : (
- +
)} diff --git a/apps/web/app/assets/images/logo-spinner-dark.gif b/apps/web/app/assets/images/logo-spinner-dark.gif deleted file mode 100644 index 8bd08325569..00000000000 Binary files a/apps/web/app/assets/images/logo-spinner-dark.gif and /dev/null differ diff --git a/apps/web/app/assets/images/logo-spinner-light.gif b/apps/web/app/assets/images/logo-spinner-light.gif deleted file mode 100644 index 8b571031424..00000000000 Binary files a/apps/web/app/assets/images/logo-spinner-light.gif and /dev/null differ diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx index 80346247684..2c6369694d7 100644 --- a/apps/web/app/root.tsx +++ b/apps/web/app/root.tsx @@ -7,7 +7,7 @@ import type { ReactNode } from "react"; import { Links, Meta, Outlet, Scripts } from "react-router"; import type { LinksFunction } from "react-router"; -import { ThemeProvider, useTheme } from "next-themes"; +import { ThemeProvider } from "next-themes"; // plane imports import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants"; // types @@ -21,7 +21,7 @@ import ogImage from "@/app/assets/og-image.png?url"; import globalStyles from "@/styles/globals.css?url"; import type { Route } from "./+types/root"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; // lib import { isStaleAssetError, recoverFromStaleAsset } from "@/lib/stale-asset-error"; // local @@ -116,14 +116,12 @@ export default function Root() { } export function HydrateFallback() { - const { resolvedTheme } = useTheme(); - - // if we are on the server or the theme is not resolved, return an empty div - if (typeof window === "undefined" || resolvedTheme === undefined) return
; - + // Propel's LogoSpinner paints both theme GIFs and CSS picks the active one via + // `data-theme`, so we do not wait on next-themes. Waiting left a blank canvas + // until `resolvedTheme` landed, which is the flash at the end of boot. return (
- +
); } diff --git a/apps/web/core/components/account/auth-forms/auth-header.tsx b/apps/web/core/components/account/auth-forms/auth-header.tsx index 239d6582918..bdbddefc022 100644 --- a/apps/web/core/components/account/auth-forms/auth-header.tsx +++ b/apps/web/core/components/account/auth-forms/auth-header.tsx @@ -9,7 +9,7 @@ import useSWR from "swr"; import { useTranslation } from "@plane/i18n"; import type { IWorkspaceMemberInvitation } from "@plane/types"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { WorkspaceLogo } from "@/components/workspace/logo"; // helpers import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper"; @@ -102,7 +102,7 @@ export const AuthHeader = observer(function AuthHeader(props: TAuthHeader) { if (isLoading) return (
- +
); diff --git a/apps/web/core/components/common/logo-spinner.tsx b/apps/web/core/components/common/logo-spinner.tsx deleted file mode 100644 index 9dfad1d8634..00000000000 --- a/apps/web/core/components/common/logo-spinner.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2023-present Plane Software, Inc. and contributors - * SPDX-License-Identifier: AGPL-3.0-only - * See the LICENSE file for details. - */ - -import { useTheme } from "next-themes"; -// assets -import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url"; -import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url"; - -export function LogoSpinner() { - const { resolvedTheme } = useTheme(); - - const logoSrc = resolvedTheme === "dark" ? LogoSpinnerDark : LogoSpinnerLight; - - return ( -
- logo -
- ); -} diff --git a/apps/web/core/components/onboarding/create-or-join-workspaces.tsx b/apps/web/core/components/onboarding/create-or-join-workspaces.tsx index 1f29b30faea..4e70d9e601f 100644 --- a/apps/web/core/components/onboarding/create-or-join-workspaces.tsx +++ b/apps/web/core/components/onboarding/create-or-join-workspaces.tsx @@ -8,9 +8,10 @@ import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { AlertOctagonOutline } from "@makeplane/propel/icons"; // plane imports +import { useTranslation } from "@plane/i18n"; import type { IWorkspaceMemberInvitation, TOnboardingSteps } from "@plane/types"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; // hooks import { useUser } from "@/hooks/store/user"; import { useInstance } from "@/hooks/store/use-instance"; @@ -35,6 +36,8 @@ export const CreateOrJoinWorkspaces = observer(function CreateOrJoinWorkspaces(p const { invitations, stepChange, finishOnboarding } = props; // states const [currentView, setCurrentView] = useState(null); + // translation + const { t } = useTranslation(); // store hooks const { data: user } = useUser(); const { config } = useInstance(); @@ -87,7 +90,7 @@ export const CreateOrJoinWorkspaces = observer(function CreateOrJoinWorkspaces(p ) ) : (
- +
)}
diff --git a/apps/web/core/components/workspace-notifications/root.tsx b/apps/web/core/components/workspace-notifications/root.tsx index 6a9012021c2..55da0d0a215 100644 --- a/apps/web/core/components/workspace-notifications/root.tsx +++ b/apps/web/core/components/workspace-notifications/root.tsx @@ -9,10 +9,11 @@ import { observer } from "mobx-react"; import useSWR from "swr"; // plane imports import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { EmptyStateCompact } from "@plane/propel/empty-state"; import { cn } from "@plane/utils"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; // hooks import { useWorkspaceNotifications } from "@/hooks/store/notifications"; import { useWorkspace } from "@/hooks/store/use-workspace"; @@ -27,6 +28,8 @@ type NotificationsRootProps = { }; export const NotificationsRoot = observer(function NotificationsRoot({ workspaceSlug }: NotificationsRootProps) { + // translation + const { t } = useTranslation(); // hooks const { currentWorkspace } = useWorkspace(); const { @@ -94,7 +97,7 @@ export const NotificationsRoot = observer(function NotificationsRoot({ workspace <> {projectMemberInfoLoader ? (
- +
) : (
- +
); diff --git a/apps/web/core/lib/wrappers/authentication-wrapper.tsx b/apps/web/core/lib/wrappers/authentication-wrapper.tsx index fc142c3002c..f436534658b 100644 --- a/apps/web/core/lib/wrappers/authentication-wrapper.tsx +++ b/apps/web/core/lib/wrappers/authentication-wrapper.tsx @@ -8,8 +8,10 @@ import type { ReactNode } from "react"; import { observer } from "mobx-react"; import { useSearchParams, usePathname } from "next/navigation"; import useSWR from "swr"; +// plane imports +import { useTranslation } from "@plane/i18n"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; // helpers import { EPageTypes } from "@/helpers/authentication.helper"; // hooks @@ -36,6 +38,8 @@ export const AuthenticationWrapper = observer(function AuthenticationWrapper(pro const nextPath = searchParams.get("next_path"); // props const { children, pageType = EPageTypes.AUTHENTICATED } = props; + // translation + const { t } = useTranslation(); // hooks const { isLoading: isUserLoading, data: currentUser, fetchCurrentUser } = useUser(); const { data: currentUserProfile } = useUserProfile(); @@ -81,7 +85,7 @@ export const AuthenticationWrapper = observer(function AuthenticationWrapper(pro if ((isUserSWRLoading || isUserLoading || workspacesLoader) && !currentUser?.id) return (
- +
); diff --git a/apps/web/core/lib/wrappers/instance-wrapper.tsx b/apps/web/core/lib/wrappers/instance-wrapper.tsx index c5beae4d0fd..372705090bc 100644 --- a/apps/web/core/lib/wrappers/instance-wrapper.tsx +++ b/apps/web/core/lib/wrappers/instance-wrapper.tsx @@ -7,8 +7,10 @@ import type { ReactNode } from "react"; import { observer } from "mobx-react"; import useSWR from "swr"; +// plane imports +import { useTranslation } from "@plane/i18n"; // components -import { LogoSpinner } from "@/components/common/logo-spinner"; +import { LogoSpinner } from "@makeplane/propel/components/logo-spinner"; import { InstanceNotReady, MaintenanceView } from "@/components/instance"; // hooks import { useInstance } from "@/hooks/store/use-instance"; @@ -19,6 +21,8 @@ type TInstanceWrapper = { const InstanceWrapper = observer(function InstanceWrapper(props: TInstanceWrapper) { const { children } = props; + // translation + const { t } = useTranslation(); // store const { isLoading, instance, error, fetchInstanceInfo } = useInstance(); @@ -32,7 +36,7 @@ const InstanceWrapper = observer(function InstanceWrapper(props: TInstanceWrappe if ((isLoading || isInstanceSWRLoading) && !instance) return (
- +
); diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index f94ebce9d00..849a9dee681 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -31,6 +31,14 @@ export default defineConfig(() => ({ }, dedupe: ["react", "react-dom", "@headlessui/react"], }, + ssr: { + // propel's LogoSpinner statically imports its GIF pair, which only a bundler can turn into a + // URL. Externalized, the server build would hand that import to Node, which cannot load a + // `.gif` — the SPA prerender then 500s. Bundling propel means Vite resolves the asset in the + // server build too, so the prerendered shell carries the same hashed `/assets/…` URL as the + // client bundle. + noExternal: ["@makeplane/propel"], + }, server: { host: "127.0.0.1", }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7e8251a913..2497cf00c82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -876,7 +876,7 @@ importers: version: 2.2.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@makeplane/propel': specifier: 'catalog:' - version: 0.2.0(@date-fns/tz@1.4.1)(@types/react@19.2.17)(date-fns@4.1.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.17) + version: 0.3.0(@date-fns/tz@1.4.1)(@types/react@19.2.17)(date-fns@4.1.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.17) '@plane/constants': specifier: workspace:* version: link:../../packages/constants @@ -1595,9 +1595,6 @@ importers: react: specifier: 19.2.8 version: 19.2.8 - react-day-picker: - specifier: 'catalog:' - version: 9.5.0(react@19.2.8) react-dom: specifier: 19.2.8 version: 19.2.8(react@19.2.8)