diff --git a/apps/space/core/hooks/oauth/core.tsx b/apps/space/core/hooks/oauth/core.tsx
new file mode 100644
index 0000000000..54fce85e05
--- /dev/null
+++ b/apps/space/core/hooks/oauth/core.tsx
@@ -0,0 +1,82 @@
+// plane imports
+import { useSearchParams } from "next/navigation";
+import { useTheme } from "next-themes";
+import { API_BASE_URL } from "@plane/constants";
+import type { TOAuthConfigs, TOAuthOption } from "@plane/types";
+// 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";
+// hooks
+import { useInstance } from "@/hooks/store/use-instance";
+
+export const useCoreOAuthConfig = (oauthActionText: string): TOAuthConfigs => {
+ //router
+ const searchParams = useSearchParams();
+ // query params
+ const next_path = searchParams.get("next_path");
+ // theme
+ const { resolvedTheme } = useTheme();
+ // store hooks
+ const { config } = useInstance();
+ // derived values
+ const isOAuthEnabled =
+ (config &&
+ (config?.is_google_enabled ||
+ config?.is_github_enabled ||
+ config?.is_gitlab_enabled ||
+ config?.is_gitea_enabled)) ||
+ false;
+ const oAuthOptions: TOAuthOption[] = [
+ {
+ id: "google",
+ text: `${oauthActionText} with Google`,
+ icon:
,
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/google/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_google_enabled,
+ },
+ {
+ id: "github",
+ text: `${oauthActionText} with GitHub`,
+ icon: (
+
+ ),
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/github/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_github_enabled,
+ },
+ {
+ id: "gitlab",
+ text: `${oauthActionText} with GitLab`,
+ icon:
,
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/gitlab/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_gitlab_enabled,
+ },
+ {
+ id: "gitea",
+ text: `${oauthActionText} with Gitea`,
+ icon:
,
+ onClick: () => {
+ window.location.assign(`${API_BASE_URL}/auth/gitea/${next_path ? `?next_path=${next_path}` : ``}`);
+ },
+ enabled: config?.is_gitea_enabled,
+ },
+ ];
+
+ return {
+ isOAuthEnabled,
+ oAuthOptions,
+ };
+};
diff --git a/apps/space/core/hooks/oauth/extended.tsx b/apps/space/core/hooks/oauth/extended.tsx
new file mode 100644
index 0000000000..d6793f9de6
--- /dev/null
+++ b/apps/space/core/hooks/oauth/extended.tsx
@@ -0,0 +1,7 @@
+// plane imports
+import type { TOAuthConfigs } from "@plane/types";
+
+export const useExtendedOAuthConfig = (_oauthActionText: string): TOAuthConfigs => ({
+ isOAuthEnabled: false,
+ oAuthOptions: [],
+});
diff --git a/apps/space/core/hooks/oauth/index.ts b/apps/space/core/hooks/oauth/index.ts
new file mode 100644
index 0000000000..2b15648737
--- /dev/null
+++ b/apps/space/core/hooks/oauth/index.ts
@@ -0,0 +1,14 @@
+// plane imports
+import type { TOAuthConfigs } from "@plane/types";
+// local imports
+import { useCoreOAuthConfig } from "./core";
+import { useExtendedOAuthConfig } from "./extended";
+
+export const useOAuthConfig = (oauthActionText: string = "Continue"): TOAuthConfigs => {
+ const coreOAuthConfig = useCoreOAuthConfig(oauthActionText);
+ const extendedOAuthConfig = useExtendedOAuthConfig(oauthActionText);
+ return {
+ isOAuthEnabled: coreOAuthConfig.isOAuthEnabled || extendedOAuthConfig.isOAuthEnabled,
+ oAuthOptions: [...coreOAuthConfig.oAuthOptions, ...extendedOAuthConfig.oAuthOptions],
+ };
+};
diff --git a/apps/space/core/store/profile.store.ts b/apps/space/core/store/profile.store.ts
index 009b46ca49..84455bc08d 100644
--- a/apps/space/core/store/profile.store.ts
+++ b/apps/space/core/store/profile.store.ts
@@ -32,13 +32,9 @@ export class ProfileStore implements IProfileStore {
last_workspace_id: undefined,
theme: {
theme: undefined,
- text: undefined,
- palette: undefined,
primary: undefined,
background: undefined,
darkPalette: undefined,
- sidebarText: undefined,
- sidebarBackground: undefined,
},
onboarding_step: {
workspace_join: false,
diff --git a/apps/space/package.json b/apps/space/package.json
index 774b08b4b7..f1fef4b763 100644
--- a/apps/space/package.json
+++ b/apps/space/package.json
@@ -18,6 +18,9 @@
},
"dependencies": {
"@bprogress/core": "catalog:",
+ "@fontsource-variable/inter": "5.2.8",
+ "@fontsource/ibm-plex-mono": "5.2.7",
+ "@fontsource/material-symbols-rounded": "5.2.30",
"@headlessui/react": "^1.7.19",
"@plane/constants": "workspace:*",
"@plane/editor": "workspace:*",
diff --git a/apps/space/styles/globals.css b/apps/space/styles/globals.css
index d179aec6b9..ba7f052d19 100644
--- a/apps/space/styles/globals.css
+++ b/apps/space/styles/globals.css
@@ -52,7 +52,7 @@
/* Progress Bar Styles */
:root {
- --bprogress-color: rgb(var(--color-primary-100)) !important;
+ --bprogress-color: var(--background-color-accent-primary);
--bprogress-height: 2.5px !important;
}
@@ -63,8 +63,8 @@
.bprogress .bar {
background: linear-gradient(
90deg,
- rgba(var(--color-primary-100), 0.8) 0%,
- rgba(var(--color-primary-100), 1) 100%
+ --alpha(var(--background-color-accent-primary) / 80%) 0%,
+ --alpha(var(--background-color-accent-primary) / 100%) 100%
) !important;
will-change: width, opacity;
}
@@ -72,7 +72,7 @@
.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;
+ 0 0 8px --alpha(var(--background-color-accent-primary) / 60%),
+ 0 0 4px --alpha(var(--background-color-accent-primary) / 40%) !important;
will-change: transform, opacity;
}
diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx
index c6b225de8f..f3a5fe3e80 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx
@@ -67,22 +67,27 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
{workspaceProjectIds && (
<>
{workspaceProjectIds.length > 0 || loader === "init-loader" ? (
-
+
-
+
{ANALYTICS_TABS.map((tab) => (
{
+ if (!tab.isDisabled) {
+ handleTabChange(tab.key);
+ }
+ }}
>
{tab.label}
diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx
index 1aba8eae83..d9f7d82b0b 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx
@@ -4,6 +4,7 @@ import { useTheme } from "next-themes";
import useSWR from "swr";
// plane imports
import { useTranslation } from "@plane/i18n";
+import type { TIssue } from "@plane/types";
import { EIssueServiceType } from "@plane/types";
import { Loader } from "@plane/ui";
// assets
@@ -12,7 +13,6 @@ import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp?u
// components
import { EmptyState } from "@/components/common/empty-state";
import { PageHead } from "@/components/core/page-title";
-import { IssueDetailRoot } from "@/components/issues/issue-detail";
// hooks
import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
@@ -21,9 +21,11 @@ import { useAppRouter } from "@/hooks/use-app-router";
// plane web imports
import { useWorkItemProperties } from "@/plane-web/hooks/use-issue-properties";
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
+import { WorkItemDetailRoot } from "@/plane-web/components/browse/workItem-detail";
+
import type { Route } from "./+types/page";
-function IssueDetailsPage({ params }: Route.ComponentProps) {
+export const IssueDetailsPage = observer(function IssueDetailsPage({ params }: Route.ComponentProps) {
// router
const router = useAppRouter();
const { workspaceSlug, workItem } = params;
@@ -35,18 +37,21 @@ function IssueDetailsPage({ params }: Route.ComponentProps) {
fetchIssueWithIdentifier,
issue: { getIssueById },
} = useIssueDetail();
- const { getProjectById } = useProject();
+ const { getProjectById, getProjectByIdentifier } = useProject();
const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme();
const [projectIdentifier, sequence_id] = workItem.split("-");
// fetching issue details
- const { data, isLoading, error } = useSWR(`ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}`, () =>
- fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
+ const { data, isLoading, error } = useSWR(
+ `ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}`,
+ () => fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id)
);
- const issueId = data?.id;
- const projectId = data?.project_id;
+
// derived values
+ const projectDetails = getProjectByIdentifier(projectIdentifier);
+ const issueId = data?.id;
+ const projectId = data?.project_id ?? projectDetails?.id ?? "";
const issue = getIssueById(issueId?.toString() || "") || undefined;
const project = (issue?.project_id && getProjectById(issue?.project_id)) || undefined;
const issueLoader = !issue || isLoading;
@@ -77,51 +82,56 @@ function IssueDetailsPage({ params }: Route.ComponentProps) {
if (data?.is_intake) {
router.push(`/${workspaceSlug}/projects/${data.project_id}/intake/?currentTab=open&inboxIssueId=${data?.id}`);
}
- }, [workspaceSlug, data]);
+ }, [workspaceSlug, data, router]);
+
+ if (error && !isLoading) {
+ return (
+ router.push(`/${workspaceSlug}/workspace-views/all-issues/`),
+ }}
+ />
+ );
+ }
+
+ if (issueLoader) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
return (
<>
- {error && !issueLoader ? (
- router.push(`/${workspaceSlug}/workspace-views/all-issues/`),
- }}
- />
- ) : issueLoader ? (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ) : (
- projectId &&
- issueId && (
-
-
-
- )
+ {workspaceSlug && projectId && issueId && (
+
+
+
)}
>
);
-}
+});
-export default observer(IssueDetailsPage);
+export default IssueDetailsPage;
diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
index 7e97e57b6d..b74886bae7 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx
@@ -14,6 +14,7 @@ import {
import { usePlatformOS } from "@plane/hooks";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
+import { IconButton } from "@plane/propel/icon-button";
import { CycleIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import type { ICustomSearchSelectOption, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
@@ -236,7 +237,6 @@ export const CycleIssuesHeader = observer(function CycleIssuesHeader() {
{
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
}}
@@ -247,9 +247,15 @@ export const CycleIssuesHeader = observer(function CycleIssuesHeader() {
)}
>
)}
-
-
-
+
>
)}
-
-
-
+
{moduleId && (
{
try {
await inviteMembersToWorkspace(workspaceSlug, data);
+ void mutateWorkspaceMembersActivity(workspaceSlug);
setInviteModal(false);
@@ -127,7 +128,7 @@ const WorkspaceMembersSettingsPage = observer(function WorkspaceMembersSettingsP
"opacity-60": !canPerformWorkspaceMemberActions,
})}
>
-
+
{t("workspace_settings.settings.members.title")}
{workspaceMemberIds && workspaceMemberIds.length > 0 && (
diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx
index 1df1df643c..f3098f675a 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx
@@ -10,7 +10,7 @@ import { SettingsHeading } from "@/components/settings/heading";
// hooks
import { useUserProfile } from "@/hooks/store/user";
-function ProfileAppearancePage() {
+const ProfileAppearancePage = observer(() => {
const { t } = useTranslation();
// hooks
const { data: userProfile } = useUserProfile();
@@ -34,6 +34,6 @@ function ProfileAppearancePage() {
>
);
-}
+});
-export default observer(ProfileAppearancePage);
+export default ProfileAppearancePage;
diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx
index cf8ff8c07f..e68070da13 100644
--- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx
+++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx
@@ -149,12 +149,12 @@ function SecurityPage() {
/>
{showPassword?.oldPassword ? (
handleShowPassword("oldPassword")}
/>
) : (
handleShowPassword("oldPassword")}
/>
)}
@@ -187,12 +187,12 @@ function SecurityPage() {
/>
{showPassword?.password ? (
handleShowPassword("password")}
/>
) : (
handleShowPassword("password")}
/>
)}
@@ -227,12 +227,12 @@ function SecurityPage() {
/>
{showPassword?.confirmPassword ? (
handleShowPassword("confirmPassword")}
/>
) : (
handleShowPassword("confirmPassword")}
/>
)}
diff --git a/apps/web/app/(all)/create-workspace/page.tsx b/apps/web/app/(all)/create-workspace/page.tsx
index 43593e8152..0228efa2dc 100644
--- a/apps/web/app/(all)/create-workspace/page.tsx
+++ b/apps/web/app/(all)/create-workspace/page.tsx
@@ -52,11 +52,11 @@ const CreateWorkspacePage = observer(function CreateWorkspacePage() {
return (
-
+
diff --git a/apps/web/app/(all)/invitations/page.tsx b/apps/web/app/(all)/invitations/page.tsx
index 656051ee38..f16a8a24e6 100644
--- a/apps/web/app/(all)/invitations/page.tsx
+++ b/apps/web/app/(all)/invitations/page.tsx
@@ -131,7 +131,7 @@ function UserInvitationsPage() {
diff --git a/apps/web/app/(all)/profile/appearance/page.tsx b/apps/web/app/(all)/profile/appearance/page.tsx
index 68ba9779e6..d0d05588d8 100644
--- a/apps/web/app/(all)/profile/appearance/page.tsx
+++ b/apps/web/app/(all)/profile/appearance/page.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { useTheme } from "next-themes";
// plane imports
@@ -6,9 +6,8 @@ import type { I_THEME_OPTION } from "@plane/constants";
import { THEME_OPTIONS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { setPromiseToast } from "@plane/propel/toast";
-import type { IUserTheme } from "@plane/types";
+import { applyCustomTheme } from "@plane/utils";
// components
-import { applyTheme, unsetCustomCssVariables } from "@plane/utils";
import { LogoSpinner } from "@/components/common/logo-spinner";
import { PageHead } from "@/components/core/page-title";
import { CustomThemeSelector } from "@/components/core/theme/custom-theme-selector";
@@ -19,46 +18,59 @@ import { ProfileSettingContentWrapper } from "@/components/profile/profile-setti
import { useUserProfile } from "@/hooks/store/user";
function ProfileAppearancePage() {
- const { t } = useTranslation();
- const { setTheme } = useTheme();
- // states
- const [currentTheme, setCurrentTheme] = useState
(null);
- // hooks
+ // store hooks
const { data: userProfile, updateUserTheme } = useUserProfile();
-
- useEffect(() => {
- if (userProfile?.theme?.theme) {
- const userThemeOption = THEME_OPTIONS.find((t) => t.value === userProfile?.theme?.theme);
- if (userThemeOption) {
- setCurrentTheme(userThemeOption);
- }
- }
+ // theme
+ const { setTheme } = useTheme();
+ // translation
+ const { t } = useTranslation();
+ // derived values
+ const currentTheme = useMemo(() => {
+ const userThemeOption = THEME_OPTIONS.find((t) => t.value === userProfile?.theme?.theme);
+ return userThemeOption || null;
}, [userProfile?.theme?.theme]);
- const handleThemeChange = (themeOption: I_THEME_OPTION) => {
- applyThemeChange({ theme: themeOption.value });
+ const handleThemeChange = useCallback(
+ async (themeOption: I_THEME_OPTION) => {
+ setTheme(themeOption.value);
- const updateCurrentUserThemePromise = updateUserTheme({ theme: themeOption.value });
- setPromiseToast(updateCurrentUserThemePromise, {
- loading: "Updating theme...",
- success: {
- title: "Success!",
- message: () => "Theme updated successfully!",
- },
- error: {
- title: "Error!",
- message: () => "Failed to Update the theme",
- },
- });
- };
+ // If switching to custom theme and user has saved custom colors, apply them immediately
+ if (
+ themeOption.value === "custom" &&
+ userProfile?.theme?.primary &&
+ userProfile?.theme?.background &&
+ userProfile?.theme?.darkPalette !== undefined
+ ) {
+ applyCustomTheme(
+ userProfile.theme.primary,
+ userProfile.theme.background,
+ userProfile.theme.darkPalette ? "dark" : "light"
+ );
+ }
- const applyThemeChange = (theme: Partial) => {
- setTheme(theme?.theme || "system");
-
- if (theme?.theme === "custom" && theme?.palette) {
- applyTheme(theme?.palette !== ",,,," ? theme?.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", false);
- } else unsetCustomCssVariables();
- };
+ const updateCurrentUserThemePromise = updateUserTheme({ theme: themeOption.value });
+ setPromiseToast(updateCurrentUserThemePromise, {
+ loading: "Updating theme...",
+ success: {
+ title: "Theme updated",
+ message: () => "Reloading to apply changes...",
+ },
+ error: {
+ title: "Error!",
+ message: () => "Failed to update theme. Please try again.",
+ },
+ });
+ // Wait for the promise to resolve, then reload after showing toast
+ try {
+ await updateCurrentUserThemePromise;
+ window.location.reload();
+ } catch (error) {
+ // Error toast already shown by setPromiseToast
+ console.error("Error updating theme:", error);
+ }
+ },
+ [setTheme, updateUserTheme, userProfile]
+ );
return (
<>
@@ -75,7 +87,7 @@ function ProfileAppearancePage() {
- {userProfile?.theme?.theme === "custom" &&
}
+ {userProfile?.theme?.theme === "custom" &&
}
) : (
diff --git a/apps/web/app/(all)/profile/security/page.tsx b/apps/web/app/(all)/profile/security/page.tsx
index f121880071..99f19bce88 100644
--- a/apps/web/app/(all)/profile/security/page.tsx
+++ b/apps/web/app/(all)/profile/security/page.tsx
@@ -147,12 +147,12 @@ function SecurityPage() {
/>
{showPassword?.oldPassword ? (
handleShowPassword("oldPassword")}
/>
) : (
handleShowPassword("oldPassword")}
/>
)}
@@ -185,12 +185,12 @@ function SecurityPage() {
/>
{showPassword?.password ? (
handleShowPassword("password")}
/>
) : (
handleShowPassword("password")}
/>
)}
@@ -225,12 +225,12 @@ function SecurityPage() {
/>
{showPassword?.confirmPassword ? (
handleShowPassword("confirmPassword")}
/>
) : (
handleShowPassword("confirmPassword")}
/>
)}
diff --git a/apps/web/app/error/dev.tsx b/apps/web/app/error/dev.tsx
index 68a2db27d7..2e87859f43 100644
--- a/apps/web/app/error/dev.tsx
+++ b/apps/web/app/error/dev.tsx
@@ -55,7 +55,7 @@ export function DevErrorComponent({ error, onGoHome, onReload }: DevErrorCompone
Error Data
-
{error.data}
+
{error.data}
@@ -95,7 +95,7 @@ export function DevErrorComponent({ error, onGoHome, onReload }: DevErrorCompone
Stack Trace
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 3638de072c..04a46a2058 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -2,8 +2,6 @@ import Script from "next/script";
// styles
import "@/styles/globals.css";
-import "@/styles/power-k.css";
-import "@/styles/emoji.css";
import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants";
@@ -78,7 +76,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
-
+
{children}
diff --git a/apps/web/app/provider.tsx b/apps/web/app/provider.tsx
index 7d6811052c..4d23fed9d3 100644
--- a/apps/web/app/provider.tsx
+++ b/apps/web/app/provider.tsx
@@ -1,5 +1,5 @@
import { lazy, Suspense } from "react";
-import { useTheme, ThemeProvider } from "next-themes";
+import { useTheme } from "next-themes";
import { SWRConfig } from "swr";
// Plane Imports
import { WEB_SWR_CONFIG } from "@plane/constants";
@@ -9,44 +9,45 @@ import { Toast } from "@plane/propel/toast";
import { resolveGeneralTheme } from "@plane/utils";
// polyfills
import "@/lib/polyfills";
-// progress bar
-import { AppProgressBar } from "@/lib/b-progress";
// mobx store provider
import { StoreProvider } from "@/lib/store-context";
-// wrappers
-import { InstanceWrapper } from "@/lib/wrappers/instance-wrapper";
// lazy imports
+const AppProgressBar = lazy(function AppProgressBar() {
+ return import("@/lib/b-progress/AppProgressBar");
+});
+
const StoreWrapper = lazy(function StoreWrapper() {
return import("@/lib/wrappers/store-wrapper");
});
-const PostHogProvider = lazy(function PostHogProvider() {
- return import("@/lib/posthog-provider");
+const InstanceWrapper = lazy(function InstanceWrapper() {
+ return import("@/lib/wrappers/instance-wrapper");
});
const ChatSupportModal = lazy(function ChatSupportModal() {
return import("@/components/global/chat-support-modal");
});
+const PostHogProvider = lazy(function PostHogProvider() {
+ return import("@/lib/posthog-provider");
+});
+
export interface IAppProvider {
children: React.ReactNode;
}
-function ToastWithTheme() {
- const { resolvedTheme } = useTheme();
- return
;
-}
-
export function AppProvider(props: IAppProvider) {
const { children } = props;
// themes
+ const { resolvedTheme } = useTheme();
+
return (
-
+ <>
-
+
@@ -58,7 +59,7 @@ export function AppProvider(props: IAppProvider) {
-
+ >
);
}
diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx
index a0d8c20764..2fbfd46c64 100644
--- a/apps/web/app/root.tsx
+++ b/apps/web/app/root.tsx
@@ -3,6 +3,7 @@ import * as Sentry from "@sentry/react-router";
import Script from "next/script";
import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router";
+import { ThemeProvider, useTheme } from "next-themes";
// plane imports
import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants";
import { cn } from "@plane/utils";
@@ -14,12 +15,18 @@ import faviconIco from "@/app/assets/favicon/favicon.ico?url";
import icon180 from "@/app/assets/icons/icon-180x180.png?url";
import icon512 from "@/app/assets/icons/icon-512x512.png?url";
import ogImage from "@/app/assets/og-image.png?url";
-import { LogoSpinner } from "@/components/common/logo-spinner";
import globalStyles from "@/styles/globals.css?url";
import type { Route } from "./+types/root";
+// components
+import { LogoSpinner } from "@/components/common/logo-spinner";
// local
import { CustomErrorComponent } from "./error";
import { AppProvider } from "./provider";
+// fonts
+import "@fontsource-variable/inter";
+import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url";
+import "@fontsource/material-symbols-rounded";
+import "@fontsource/ibm-plex-mono";
const APP_TITLE = "Plane | Simple, extensible, open-source project management tool.";
@@ -33,13 +40,20 @@ export const links: LinksFunction = () => [
{ rel: "apple-touch-icon", sizes: "512x512", href: icon512 },
{ rel: "manifest", href: "/manifest.json" },
{ rel: "stylesheet", href: globalStyles },
+ {
+ rel: "preload",
+ href: interVariableWoff2,
+ as: "font",
+ type: "font/woff2",
+ crossOrigin: "anonymous",
+ },
];
export function Layout({ children }: { children: ReactNode }) {
const isSessionRecorderEnabled = parseInt(process.env.VITE_ENABLE_SESSION_RECORDER || "0");
return (
-
+
@@ -54,14 +68,12 @@ export function Layout({ children }: { children: ReactNode }) {
-
+
-
-
- {children}
-
-
+
+ {children}
+
{!!isSessionRecorderEnabled && process.env.VITE_SESSION_RECORDER_KEY && (