diff --git a/apps/admin/app/root.tsx b/apps/admin/app/root.tsx index 128e36f6a..746c2099e 100644 --- a/apps/admin/app/root.tsx +++ b/apps/admin/app/root.tsx @@ -1,11 +1,11 @@ import type { ReactNode } from "react"; import { Links, Meta, Outlet, Scripts } from "react-router"; import type { LinksFunction } from "react-router"; -import "../styles/globals.css"; 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"; +import globalStyles from "@/styles/globals.css?url"; import type { Route } from "./+types/root"; import { AppProviders } from "./providers"; @@ -19,6 +19,7 @@ export const links: LinksFunction = () => [ { rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 }, { rel: "shortcut icon", href: faviconIco }, { rel: "manifest", href: `/site.webmanifest.json` }, + { rel: "stylesheet", href: globalStyles }, ]; export function Layout({ children }: { children: ReactNode }) { @@ -56,6 +57,10 @@ export default function Root() { return ; } +export function HydrateFallback() { + return null; +} + export function ErrorBoundary() { return (
diff --git a/apps/space/app/[workspaceSlug]/[projectId]/page.tsx b/apps/space/app/[workspaceSlug]/[projectId]/page.tsx index 5049ef441..40277ce9b 100644 --- a/apps/space/app/[workspaceSlug]/[projectId]/page.tsx +++ b/apps/space/app/[workspaceSlug]/[projectId]/page.tsx @@ -1,16 +1,16 @@ "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"; +import type { Route } from "./+types/page"; const publishService = new SitesProjectPublishService(); -export const clientLoader = async ({ params, request }: ClientLoaderFunctionArgs) => { +export const clientLoader = async ({ params, request }: Route.ClientLoaderArgs) => { const { workspaceSlug, projectId } = params; // Validate required params diff --git a/apps/space/app/root.tsx b/apps/space/app/root.tsx index de37a7c24..680ce0b55 100644 --- a/apps/space/app/root.tsx +++ b/apps/space/app/root.tsx @@ -1,12 +1,11 @@ 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"; +import globalStyles from "@/styles/globals.css?url"; // types import type { Route } from "./+types/root"; // local imports @@ -22,6 +21,7 @@ export const links: LinksFunction = () => [ { rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 }, { rel: "shortcut icon", href: faviconIco }, { rel: "manifest", href: `/site.webmanifest.json` }, + { rel: "stylesheet", href: globalStyles }, ]; export function Layout({ children }: { children: React.ReactNode }) { @@ -61,6 +61,10 @@ export default function Root() { return ; } +export function HydrateFallback() { + return null; +} + export function ErrorBoundary() { return ; } diff --git a/apps/space/package.json b/apps/space/package.json index a0e2ea74e..e6f00ab94 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -20,7 +20,7 @@ "@bprogress/core": "catalog:", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@headlessui/react": "^1.7.13", + "@headlessui/react": "^1.7.19", "@plane/constants": "workspace:*", "@plane/editor": "workspace:*", "@plane/i18n": "workspace:*", diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 000000000..90b7c4387 --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,11 @@ +node_modules +.next +.react-router +.vite +.turbo +build +dist +*.log +.env* +!.env.example + diff --git a/apps/web/.eslintignore b/apps/web/.eslintignore index e29e17a08..99b52a07f 100644 --- a/apps/web/.eslintignore +++ b/apps/web/.eslintignore @@ -1,4 +1,7 @@ .next/* +.react-router/* +.vite/* +build/* out/* public/* core/local-db/worker/wa-sqlite/src/* diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.cjs similarity index 90% rename from apps/web/.eslintrc.js rename to apps/web/.eslintrc.cjs index a0bc76d5d..d6a1fc833 100644 --- a/apps/web/.eslintrc.js +++ b/apps/web/.eslintrc.cjs @@ -1,6 +1,7 @@ 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 }], diff --git a/apps/web/.prettierignore b/apps/web/.prettierignore index e841c6b32..9705027a7 100644 --- a/apps/web/.prettierignore +++ b/apps/web/.prettierignore @@ -1,5 +1,9 @@ .next +.react-router +.vite .turbo out/ dist/ -build/ \ No newline at end of file +build/ +node_modules +pnpm-lock.yaml diff --git a/apps/web/Dockerfile.web b/apps/web/Dockerfile.web index 873edfb15..04e04a3af 100644 --- a/apps/web/Dockerfile.web +++ b/apps/web/Dockerfile.web @@ -71,50 +71,16 @@ ENV TURBO_TELEMETRY_DISABLED=1 RUN pnpm turbo run build --filter=web # ***************************************************************************** -# STAGE 3: Copy the project and start it +# STAGE 3: Serve with nginx # ***************************************************************************** -FROM base AS runner -WORKDIR /app +FROM nginx:1.27-alpine AS production -# 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/web/.next/standalone ./ -COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static -COPY --from=installer /app/apps/web/public ./apps/web/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 - -ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode" -ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_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_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_WEB_BASE_URL="" -ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL - -ENV NEXT_TELEMETRY_DISABLED=1 -ENV TURBO_TELEMETRY_DISABLED=1 +COPY apps/web/nginx/nginx.conf /etc/nginx/nginx.conf +COPY --from=installer /app/apps/web/build/client /usr/share/nginx/html EXPOSE 3000 -CMD ["node", "apps/web/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/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/layout.tsx index 1ee1b3c3d..e1db9309f 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/layout.tsx @@ -1,16 +1,19 @@ "use client"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local imports import { WorkspaceActiveCycleHeader } from "./header"; -export default function WorkspaceActiveCycleLayout({ children }: { children: React.ReactNode }) { +export default function WorkspaceActiveCycleLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/page.tsx index 3b3e82c8f..eb5d4bdcc 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/active-cycles/page.tsx @@ -8,7 +8,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace"; // plane web components import { WorkspaceActiveCyclesRoot } from "@/plane-web/components/active-cycles"; -const WorkspaceActiveCyclesPage = observer(() => { +function WorkspaceActiveCyclesPage() { const { currentWorkspace } = useWorkspace(); // derived values const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Active Cycles` : undefined; @@ -19,6 +19,6 @@ const WorkspaceActiveCyclesPage = observer(() => { ); -}); +} -export default WorkspaceActiveCyclesPage; +export default observer(WorkspaceActiveCyclesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/layout.tsx index 29d4a54e6..497e40366 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/layout.tsx @@ -1,14 +1,17 @@ "use client"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { WorkspaceAnalyticsHeader } from "./header"; -export default function WorkspaceAnalyticsTabLayout({ children }: { children: React.ReactNode }) { +export default function WorkspaceAnalyticsTabLayout() { return ( <> } /> - {children} + + + ); } 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 cf092aab5..a805da783 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx @@ -19,17 +19,9 @@ import { useProject } from "@/hooks/store/use-project"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUserPermissions } from "@/hooks/store/user"; import { getAnalyticsTabs } from "@/plane-web/components/analytics/tabs"; +import type { Route } from "./+types/page"; -type Props = { - params: { - tabId: string; - workspaceSlug: string; - }; -}; - -const AnalyticsPage = observer((props: Props) => { - // props - const { params } = props; +function AnalyticsPage({ params }: Route.ComponentProps) { const { tabId } = params; // hooks @@ -68,7 +60,7 @@ const AnalyticsPage = observer((props: Props) => { })), [ANALYTICS_TABS, router, currentWorkspace?.slug] ); - const defaultTab = tabId || ANALYTICS_TABS[0].key; + const defaultTab = tabId; return ( <> @@ -111,6 +103,6 @@ const AnalyticsPage = observer((props: Props) => { )} ); -}); +} -export default AnalyticsPage; +export default observer(AnalyticsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/layout.tsx index f4cac6177..c9d8faf04 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/layout.tsx @@ -1,15 +1,18 @@ "use client"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectIssueDetailsHeader } from "./header"; -export default function ProjectIssueDetailsLayout({ children }: { children: React.ReactNode }) { +export default function ProjectIssueDetailsLayout() { return ( <> } /> - {children} + + + ); } 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 dabb43eb7..9462653f0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx @@ -1,14 +1,16 @@ "use client"; -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; import useSWR from "swr"; // plane imports import { useTranslation } from "@plane/i18n"; import { EIssueServiceType } from "@plane/types"; import { Loader } from "@plane/ui"; +// assets +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 { PageHead } from "@/components/core/page-title"; @@ -17,17 +19,16 @@ import { IssueDetailRoot } from "@/components/issues/issue-detail"; import { useAppTheme } from "@/hooks/store/use-app-theme"; import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useProject } from "@/hooks/store/use-project"; -// assets 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 emptyIssueDark from "@/public/empty-state/search/issues-dark.webp"; -import emptyIssueLight from "@/public/empty-state/search/issues-light.webp"; +import type { Route } from "./+types/page"; -const IssueDetailsPage = observer(() => { +function IssueDetailsPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, workItem } = useParams(); + const { workspaceSlug, workItem } = params; // hooks const { resolvedTheme } = useTheme(); // store hooks @@ -39,15 +40,11 @@ const IssueDetailsPage = observer(() => { const { getProjectById } = useProject(); const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme(); - const projectIdentifier = workItem?.toString().split("-")[0]; - const sequence_id = workItem?.toString().split("-")[1]; + const [projectIdentifier, sequence_id] = workItem.split("-"); // fetching issue details - const { data, isLoading, error } = useSWR( - workspaceSlug && workItem ? `ISSUE_DETAIL_${workspaceSlug}_${projectIdentifier}_${sequence_id}` : null, - workspaceSlug && workItem - ? () => fetchIssueWithIdentifier(workspaceSlug.toString(), projectIdentifier, sequence_id) - : null + 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; @@ -113,14 +110,13 @@ const IssueDetailsPage = observer(() => {
) : ( - workspaceSlug && projectId && issueId && ( - + @@ -128,6 +124,6 @@ const IssueDetailsPage = observer(() => { )} ); -}); +} -export default IssueDetailsPage; +export default observer(IssueDetailsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/layout.tsx index 7629f6ed3..e2b6a06e6 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/layout.tsx @@ -1,16 +1,19 @@ "use client"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local imports import { WorkspaceDraftHeader } from "./header"; -export default function WorkspaceDraftLayout({ children }: { children: React.ReactNode }) { +export default function WorkspaceDraftLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/page.tsx index 93c9b79ce..b36a31c78 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/drafts/page.tsx @@ -1,19 +1,14 @@ "use client"; -import { useParams } from "next/navigation"; // components import { PageHead } from "@/components/core/page-title"; import { WorkspaceDraftIssuesRoot } from "@/components/issues/workspace-draft"; +import type { Route } from "./+types/page"; -const WorkspaceDraftPage = () => { - // router - const { workspaceSlug: routeWorkspaceSlug } = useParams(); +function WorkspaceDraftPage({ params }: Route.ComponentProps) { + const { workspaceSlug } = params; const pageTitle = "Workspace Draft"; - // derived values - const workspaceSlug = (routeWorkspaceSlug as string) || undefined; - - if (!workspaceSlug) return null; return ( <> @@ -22,6 +17,6 @@ const WorkspaceDraftPage = () => { ); -}; +} export default WorkspaceDraftPage; diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx index 9e381c903..c780c8aad 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/layout.tsx @@ -1,33 +1,30 @@ "use client"; import { observer } from "mobx-react"; +import { Outlet } from "react-router"; import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; // plane web components import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper"; import { ProjectAppSidebar } from "./_sidebar"; -const WorkspaceLayoutContent = observer(({ children }: { children: React.ReactNode }) => ( - <> - - -
-
-
- -
- {children} -
-
-
- - -)); - -export default function WorkspaceLayout({ children }: { children: React.ReactNode }) { +function WorkspaceLayout() { return ( - {children} + + +
+
+
+ +
+ +
+
+
+ ); } + +export default observer(WorkspaceLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/layout.tsx index f8fd3a0f4..d0083781d 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/layout.tsx @@ -1,13 +1,16 @@ "use client"; +import { Outlet } from "react-router"; // components import { NotificationsSidebarRoot } from "@/components/workspace-notifications/sidebar"; -export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectInboxIssuesLayout() { return (
-
{children}
+
+ +
); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/page.tsx index 4521cf436..2932f94f0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/notifications/page.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // plane imports import { useTranslation } from "@plane/i18n"; // components @@ -9,9 +8,10 @@ import { PageHead } from "@/components/core/page-title"; import { NotificationsRoot } from "@/components/workspace-notifications"; // hooks import { useWorkspace } from "@/hooks/store/use-workspace"; +import type { Route } from "./+types/page"; -const WorkspaceDashboardPage = observer(() => { - const { workspaceSlug } = useParams(); +function WorkspaceDashboardPage({ params }: Route.ComponentProps) { + const { workspaceSlug } = params; // plane hooks const { t } = useTranslation(); // hooks @@ -24,9 +24,9 @@ const WorkspaceDashboardPage = observer(() => { return ( <> - + ); -}); +} -export default WorkspaceDashboardPage; +export default observer(WorkspaceDashboardPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/page.tsx index 446a965ae..d5ee26fa1 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/page.tsx @@ -12,7 +12,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace"; // local components import { WorkspaceDashboardHeader } from "./header"; -const WorkspaceDashboardPage = observer(() => { +function WorkspaceDashboardPage() { const { currentWorkspace } = useWorkspace(); const { t } = useTranslation(); // derived values @@ -27,6 +27,6 @@ const WorkspaceDashboardPage = observer(() => { ); -}); +} -export default WorkspaceDashboardPage; +export default observer(WorkspaceDashboardPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/[profileViewId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/[profileViewId]/page.tsx index aac7ed459..fdfaa22e1 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/[profileViewId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/[profileViewId]/page.tsx @@ -1,10 +1,10 @@ "use client"; import React from "react"; -import { useParams } from "next/navigation"; // components import { PageHead } from "@/components/core/page-title"; import { ProfileIssuesPage } from "@/components/profile/profile-issues"; +import type { Route } from "./+types/page"; const ProfilePageHeader = { assigned: "Profile - Assigned", @@ -12,10 +12,14 @@ const ProfilePageHeader = { subscribed: "Profile - Subscribed", }; -const ProfileIssuesTypePage = () => { - const { profileViewId } = useParams() as { profileViewId: "assigned" | "subscribed" | "created" | undefined }; +function isValidProfileViewId(viewId: string): viewId is keyof typeof ProfilePageHeader { + return viewId in ProfilePageHeader; +} - if (!profileViewId) return null; +function ProfileIssuesTypePage({ params }: Route.ComponentProps) { + const { profileViewId } = params; + + if (!isValidProfileViewId(profileViewId)) return null; const header = ProfilePageHeader[profileViewId]; @@ -25,6 +29,6 @@ const ProfileIssuesTypePage = () => { ); -}; +} export default ProfileIssuesTypePage; diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx index cc9a789ec..f079a1e77 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx @@ -15,7 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user"; const PER_PAGE = 100; -const ProfileActivityPage = observer(() => { +function ProfileActivityPage() { // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); @@ -69,6 +69,6 @@ const ProfileActivityPage = observer(() => {
); -}); +} -export default ProfileActivityPage; +export default observer(ProfileActivityPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx index f89314059..50f1bdc56 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx @@ -1,7 +1,8 @@ "use client"; import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; +import { usePathname } from "next/navigation"; +import { Outlet } from "react-router"; import useSWR from "swr"; // components import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; @@ -16,20 +17,16 @@ import { useUserPermissions } from "@/hooks/store/user"; import useSize from "@/hooks/use-window-size"; // local components import { UserService } from "@/services/user.service"; +import type { Route } from "./+types/layout"; import { UserProfileHeader } from "./header"; import { ProfileIssuesMobileHeader } from "./mobile-header"; import { ProfileNavbar } from "./navbar"; const userService = new UserService(); -type Props = { - children: React.ReactNode; -}; - -const UseProfileLayout: React.FC = observer((props) => { - const { children } = props; +function UseProfileLayout({ params }: Route.ComponentProps) { // router - const { workspaceSlug, userId } = useParams(); + const { workspaceSlug, userId } = params; const pathname = usePathname(); // store hooks const { allowPermissions } = useUserPermissions(); @@ -43,11 +40,8 @@ const UseProfileLayout: React.FC = observer((props) => { const windowSize = useSize(); const isSmallerScreen = windowSize[0] >= 768; - const { data: userProjectsData } = useSWR( - workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null, - workspaceSlug && userId - ? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString()) - : null + const { data: userProjectsData } = useSWR(USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug, userId), () => + userService.getUserProfileProjectsSegregation(workspaceSlug, userId) ); // derived values const isAuthorizedPath = @@ -60,7 +54,7 @@ const UseProfileLayout: React.FC = observer((props) => { return ( <> {/* Passing the type prop from the current route value as we need the header as top most component. - TODO: We are depending on the route path to handle the mobile header type. If the path changes, this logic will break. */} + TODO: We are depending on the route path to handle the mobile header type. If the path changes, this logic will break. */}
= observer((props) => {
{isAuthorized || !isAuthorizedPath ? ( -
{children}
+
+ +
) : (
{t("you_do_not_have_the_permission_to_access_this_page")} @@ -93,6 +89,6 @@ const UseProfileLayout: React.FC = observer((props) => {
); -}); +} -export default UseProfileLayout; +export default observer(UseProfileLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/page.tsx index a7b8aad77..b63ce1aff 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/page.tsx @@ -1,6 +1,5 @@ "use client"; -import { useParams } from "next/navigation"; import useSWR from "swr"; // plane imports import { GROUP_CHOICES } from "@plane/constants"; @@ -18,15 +17,15 @@ import { ProfileWorkload } from "@/components/profile/overview/workload"; import { USER_PROFILE_DATA } from "@/constants/fetch-keys"; // services import { UserService } from "@/services/user.service"; +import type { Route } from "./+types/page"; const userService = new UserService(); -export default function ProfileOverviewPage() { - const { workspaceSlug, userId } = useParams(); +export default function ProfileOverviewPage({ params }: Route.ComponentProps) { + const { workspaceSlug, userId } = params; const { t } = useTranslation(); - const { data: userProfile } = useSWR( - workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null, - workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null + const { data: userProfile } = useSWR(USER_PROFILE_DATA(workspaceSlug, userId), () => + userService.getUserProfileData(workspaceSlug, userId) ); const stateDistribution: IUserStateDistribution[] = Object.keys(GROUP_CHOICES).map((key) => { diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx index c46c53173..7732b8504 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx @@ -1,15 +1,18 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectArchivesHeader } from "../header"; -export default function ProjectArchiveCyclesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectArchiveCyclesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx index 9894b9ca6..d99c818fa 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx @@ -1,21 +1,21 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // components import { PageHead } from "@/components/core/page-title"; import { ArchivedCycleLayoutRoot } from "@/components/cycles/archived-cycles"; import { ArchivedCyclesHeader } from "@/components/cycles/archived-cycles/header"; // hooks import { useProject } from "@/hooks/store/use-project"; +import type { Route } from "./+types/page"; -const ProjectArchivedCyclesPage = observer(() => { +function ProjectArchivedCyclesPage({ params }: Route.ComponentProps) { // router - const { projectId } = useParams(); + const { projectId } = params; // store hooks const { getProjectById } = useProject(); // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name && `${project?.name} - Archived cycles`; return ( @@ -27,6 +27,6 @@ const ProjectArchivedCyclesPage = observer(() => {
); -}); +} -export default ProjectArchivedCyclesPage; +export default observer(ProjectArchivedCyclesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx index 45430c2bf..005c66c86 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx @@ -1,7 +1,7 @@ "use client"; import { observer } from "mobx-react"; -import { useParams, useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import useSWR from "swr"; // ui import { Banner } from "@plane/propel/banner"; @@ -15,10 +15,11 @@ import { IssueDetailRoot } from "@/components/issues/issue-detail"; // hooks import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useProject } from "@/hooks/store/use-project"; +import type { Route } from "./+types/page"; -const ArchivedIssueDetailsPage = observer(() => { +function ArchivedIssueDetailsPage({ params }: Route.ComponentProps) { // router - const { workspaceSlug, projectId, archivedIssueId } = useParams(); + const { workspaceSlug, projectId, archivedIssueId } = params; const router = useRouter(); // states // hooks @@ -29,17 +30,12 @@ const ArchivedIssueDetailsPage = observer(() => { const { getProjectById } = useProject(); - const { isLoading } = useSWR( - workspaceSlug && projectId && archivedIssueId - ? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}` - : null, - workspaceSlug && projectId && archivedIssueId - ? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString()) - : null + const { isLoading } = useSWR(`ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`, () => + fetchIssue(workspaceSlug, projectId, archivedIssueId) ); // derived values - const issue = archivedIssueId ? getIssueById(archivedIssueId.toString()) : undefined; + const issue = getIssueById(archivedIssueId); const project = issue ? getProjectById(issue?.project_id ?? "") : undefined; const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined; @@ -84,20 +80,18 @@ const ArchivedIssueDetailsPage = observer(() => { />
- {workspaceSlug && projectId && archivedIssueId && ( - - )} +
)} ); -}); +} -export default ArchivedIssueDetailsPage; +export default observer(ArchivedIssueDetailsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx index 74eb8949d..77f0c854a 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx @@ -1,15 +1,18 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectArchivedIssueDetailsHeader } from "./header"; -export default function ProjectArchivedIssueDetailLayout({ children }: { children: React.ReactNode }) { +export default function ProjectArchivedIssueDetailLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx index 321ab8a62..61105a0d4 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx @@ -1,15 +1,18 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectArchivesHeader } from "../../header"; -export default function ProjectArchiveIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectArchiveIssuesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx index ceb24bf3c..dfb32d9bc 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx @@ -1,21 +1,21 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // components import { PageHead } from "@/components/core/page-title"; import { ArchivedIssuesHeader } from "@/components/issues/archived-issues-header"; import { ArchivedIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/archived-issue-layout-root"; // hooks import { useProject } from "@/hooks/store/use-project"; +import type { Route } from "./+types/page"; -const ProjectArchivedIssuesPage = observer(() => { +function ProjectArchivedIssuesPage({ params }: Route.ComponentProps) { // router - const { projectId } = useParams(); + const { projectId } = params; // store hooks const { getProjectById } = useProject(); // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name && `${project?.name} - Archived work items`; return ( @@ -27,6 +27,6 @@ const ProjectArchivedIssuesPage = observer(() => {
); -}); +} -export default ProjectArchivedIssuesPage; +export default observer(ProjectArchivedIssuesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx index ee72018ac..e7ab5f747 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx @@ -1,15 +1,18 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectArchivesHeader } from "../header"; -export default function ProjectArchiveModulesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectArchiveModulesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx index 1edb13e23..3187e73ef 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx @@ -1,20 +1,20 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // components import { PageHead } from "@/components/core/page-title"; import { ArchivedModuleLayoutRoot, ArchivedModulesHeader } from "@/components/modules"; // hooks import { useProject } from "@/hooks/store/use-project"; +import type { Route } from "./+types/page"; -const ProjectArchivedModulesPage = observer(() => { +function ProjectArchivedModulesPage({ params }: Route.ComponentProps) { // router - const { projectId } = useParams(); + const { projectId } = params; // store hooks const { getProjectById } = useProject(); // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name && `${project?.name} - Archived modules`; return ( @@ -26,6 +26,6 @@ const ProjectArchivedModulesPage = observer(() => {
); -}); +} -export default ProjectArchivedModulesPage; +export default observer(ProjectArchivedModulesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx index 9f6302140..b5ac74dad 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx @@ -1,9 +1,10 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // plane imports import { cn } from "@plane/utils"; +// assets +import emptyCycle from "@/app/assets/empty-state/cycle.svg?url"; // components import { EmptyState } from "@/components/common/empty-state"; import { PageHead } from "@/components/core/page-title"; @@ -15,13 +16,12 @@ import { useCycle } from "@/hooks/store/use-cycle"; import { useProject } from "@/hooks/store/use-project"; import { useAppRouter } from "@/hooks/use-app-router"; import useLocalStorage from "@/hooks/use-local-storage"; -// assets -import emptyCycle from "@/public/empty-state/cycle.svg"; +import type { Route } from "./+types/page"; -const CycleDetailPage = observer(() => { +function CycleDetailPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, projectId, cycleId } = useParams(); + const { workspaceSlug, projectId, cycleId } = params; // store hooks const { getCycleById, loader } = useCycle(); const { getProjectById } = useProject(); @@ -30,14 +30,14 @@ const CycleDetailPage = observer(() => { const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", false); useCyclesDetails({ - workspaceSlug: workspaceSlug?.toString(), - projectId: projectId.toString(), - cycleId: cycleId.toString(), + workspaceSlug, + projectId, + cycleId, }); // derived values const isSidebarCollapsed = storedValue ? (storedValue === true ? true : false) : false; - const cycle = cycleId ? getCycleById(cycleId.toString()) : undefined; - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const cycle = getCycleById(cycleId); + const project = getProjectById(projectId); const pageTitle = project?.name && cycle?.name ? `${project?.name} - ${cycle?.name}` : undefined; /** @@ -46,7 +46,6 @@ const CycleDetailPage = observer(() => { const toggleSidebar = () => setValue(!isSidebarCollapsed); // const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; - return ( <> @@ -66,7 +65,7 @@ const CycleDetailPage = observer(() => {
- {cycleId && !isSidebarCollapsed && ( + {!isSidebarCollapsed && (
{ >
)} @@ -89,6 +88,6 @@ const CycleDetailPage = observer(() => { )} ); -}); +} -export default CycleDetailPage; +export default observer(CycleDetailPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx index 40872f0b4..52b4d42e4 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx @@ -1,16 +1,19 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { CycleIssuesHeader } from "./header"; import { CycleIssuesMobileHeader } from "./mobile-header"; -export default function ProjectCycleIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectCycleIssuesLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx index a3caddf30..002ab5537 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx @@ -1,16 +1,19 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { CyclesListHeader } from "./header"; import { CyclesListMobileHeader } from "./mobile-header"; -export default function ProjectCyclesListLayout({ children }: { children: React.ReactNode }) { +export default function ProjectCyclesListLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx index a4dbf9d64..60285485f 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx @@ -2,8 +2,8 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // plane imports +import { useTheme } from "next-themes"; import { EUserPermissionsLevel, CYCLE_TRACKER_ELEMENTS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { EmptyStateDetailed } from "@plane/propel/empty-state"; @@ -12,6 +12,10 @@ import { EUserProjectRoles } from "@plane/types"; // components import { Header, EHeaderVariant } from "@plane/ui"; import { calculateTotalFilters } from "@plane/utils"; +// assets +import darkEmptyState from "@/app/assets/empty-state/disabled-feature/cycles-dark.webp?url"; +import lightEmptyState from "@/app/assets/empty-state/disabled-feature/cycles-light.webp?url"; +// components import { PageHead } from "@/components/core/page-title"; import { CycleAppliedFiltersList } from "@/components/cycles/applied-filters"; import { CyclesView } from "@/components/cycles/cycles-view"; @@ -24,9 +28,9 @@ import { useCycleFilter } from "@/hooks/store/use-cycle-filter"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +import type { Route } from "./+types/page"; -const ProjectCyclesPage = observer(() => { +function ProjectCyclesPage({ params }: Route.ComponentProps) { // states const [createModal, setCreateModal] = useState(false); // store hooks @@ -34,35 +38,34 @@ const ProjectCyclesPage = observer(() => { const { getProjectById, currentProjectDetails } = useProject(); // router const router = useAppRouter(); - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // cycle filters hook const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter(); const { allowPermissions } = useUserPermissions(); // derived values + const resolvedEmptyState = resolvedTheme === "light" ? lightEmptyState : darkEmptyState; const totalCycles = currentProjectCycleIds?.length ?? 0; - const project = projectId ? getProjectById(projectId?.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name ? `${project?.name} - ${t("common.cycles", { count: 2 })}` : undefined; const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); const hasMemberLevelPermission = allowPermissions( [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER], EUserPermissionsLevel.PROJECT ); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" }); const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => { - if (!projectId) return; let newValues = currentProjectFilters?.[key] ?? []; if (!value) newValues = []; else newValues = newValues.filter((val) => val !== value); - updateFilters(projectId.toString(), { [key]: newValues }); + updateFilters(projectId, { [key]: newValues }); }; - if (!workspaceSlug || !projectId) return <>; - // No access to cycle if (currentProjectDetails?.cycle_view === false) return ( @@ -70,7 +73,7 @@ const ProjectCyclesPage = observer(() => { { @@ -89,8 +92,8 @@ const ProjectCyclesPage = observer(() => {
setCreateModal(false)} /> @@ -117,18 +120,18 @@ const ProjectCyclesPage = observer(() => {
clearAllFilters(projectId.toString())} + handleClearAllFilters={() => clearAllFilters(projectId)} handleRemoveFilter={handleRemoveFilter} />
)} - + )}
); -}); +} -export default ProjectCyclesPage; +export default observer(ProjectCyclesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx index 72573c449..24e96477e 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx @@ -1,15 +1,18 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectInboxHeader } from "@/plane-web/components/projects/settings/intake/header"; -export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectInboxIssuesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx index 1190cdae7..e8cedf8e1 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx @@ -1,10 +1,14 @@ "use client"; import { observer } from "mobx-react"; -import { useParams, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; +import { useTheme } from "next-themes"; // plane imports import { EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { EUserProjectRoles, EInboxIssueCurrentTab } from "@plane/types"; +// assets +import darkIntakeAsset from "@/app/assets/empty-state/disabled-feature/intake-dark.webp?url"; +import lightIntakeAsset from "@/app/assets/empty-state/disabled-feature/intake-light.webp?url"; // components import { PageHead } from "@/components/core/page-title"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; @@ -13,15 +17,17 @@ import { InboxIssueRoot } from "@/components/inbox"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +import type { Route } from "./+types/page"; -const ProjectInboxPage = observer(() => { +function ProjectInboxPage({ params }: Route.ComponentProps) { /// router const router = useAppRouter(); - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; const searchParams = useSearchParams(); const navigationTab = searchParams.get("currentTab"); const inboxIssueId = searchParams.get("inboxIssueId"); + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // hooks @@ -29,7 +35,7 @@ const ProjectInboxPage = observer(() => { const { allowPermissions } = useUserPermissions(); // derived values const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/intake" }); + const resolvedPath = resolvedTheme === "light" ? lightIntakeAsset : darkIntakeAsset; // No access to inbox if (currentProjectDetails?.inbox_view === false) @@ -65,22 +71,20 @@ const ProjectInboxPage = observer(() => { : EInboxIssueCurrentTab.CLOSED : undefined; - if (!workspaceSlug || !projectId) return <>; - return (
); -}); +} -export default ProjectInboxPage; +export default observer(ProjectInboxPage); 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 e63f7282a..726686e78 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 @@ -1,64 +1,68 @@ "use client"; -import { useEffect } from "react"; -import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; -import useSWR from "swr"; +import { redirect } from "react-router"; import { useTranslation } from "@plane/i18n"; +// assets +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"; // hooks import { useAppRouter } from "@/hooks/use-app-router"; -// assets -import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp"; -import emptyIssueLight from "@/public/empty-state/search/issues-light.webp"; // services import { IssueService } from "@/services/issue/issue.service"; +// types +import type { Route } from "./+types/page"; const issueService = new IssueService(); -const IssueDetailsPage = observer(() => { +export async function clientLoader({ params }: Route.ClientLoaderArgs) { + const { workspaceSlug, projectId, issueId } = params; + + try { + const data = await issueService.getIssueMetaFromURL(workspaceSlug, projectId, issueId); + + if (data) { + throw redirect(`/${workspaceSlug}/browse/${data.project_identifier}-${data.sequence_id}`); + } + + return { error: true, workspaceSlug }; + } catch (error) { + // If it's a redirect, rethrow it + if (error instanceof Response) { + throw error; + } + // Otherwise return error state + return { error: true, workspaceSlug }; + } +} + +export default function IssueDetailsPage({ loaderData }: Route.ComponentProps) { const router = useAppRouter(); const { t } = useTranslation(); - const { workspaceSlug, projectId, issueId } = useParams(); const { resolvedTheme } = useTheme(); - const { data, isLoading, error } = useSWR( - workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_META_${workspaceSlug}_${projectId}_${issueId}` : null, - workspaceSlug && projectId && issueId - ? () => issueService.getIssueMetaFromURL(workspaceSlug.toString(), projectId.toString(), issueId.toString()) - : null - ); - - useEffect(() => { - if (data) { - router.push(`/${workspaceSlug}/browse/${data.project_identifier}-${data.sequence_id}`); - } - }, [workspaceSlug, data]); - - return ( -
- {error ? ( + if (loaderData.error) { + return ( +
router.push(`/${workspaceSlug}/workspace-views/all-issues/`), + onClick: () => router.push(`/${loaderData.workspaceSlug}/workspace-views/all-issues/`), }} /> - ) : isLoading ? ( - <> - - - ) : ( - <> - )} +
+ ); + } + + return ( +
+
); -}); - -export default IssueDetailsPage; +} diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx index c2df9c4e5..ffaf086c0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx @@ -1,16 +1,19 @@ "use client"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectIssuesHeader } from "./header"; import { ProjectIssuesMobileHeader } from "./mobile-header"; -export default function ProjectIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectIssuesLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx index 63162e102..17955c562 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx @@ -1,8 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import Head from "next/head"; -import { useParams } from "next/navigation"; // i18n import { useTranslation } from "@plane/i18n"; // components @@ -10,35 +8,27 @@ import { PageHead } from "@/components/core/page-title"; import { ProjectLayoutRoot } from "@/components/issues/issue-layouts/roots/project-layout-root"; // hooks import { useProject } from "@/hooks/store/use-project"; +import type { Route } from "./+types/page"; -const ProjectIssuesPage = observer(() => { - const { projectId } = useParams(); +function ProjectIssuesPage({ params }: Route.ComponentProps) { + const { projectId } = params; // i18n const { t } = useTranslation(); // store const { getProjectById } = useProject(); - if (!projectId) { - return <>; - } - // derived values - const project = getProjectById(projectId.toString()); + const project = getProjectById(projectId); const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization return ( <> - - - {project?.name} - {t("issue.label", { count: 2 })} - -
); -}); +} -export default ProjectIssuesPage; +export default observer(ProjectIssuesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx index 5b1fa1b18..bcb150140 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx @@ -1,27 +1,27 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import useSWR from "swr"; -// components +// plane imports import { cn } from "@plane/utils"; +// assets +import emptyModule from "@/app/assets/empty-state/module.svg?url"; +// components import { EmptyState } from "@/components/common/empty-state"; import { PageHead } from "@/components/core/page-title"; import { ModuleLayoutRoot } from "@/components/issues/issue-layouts/roots/module-layout-root"; import { ModuleAnalyticsSidebar } from "@/components/modules"; -// helpers // hooks import { useModule } from "@/hooks/store/use-module"; import { useProject } from "@/hooks/store/use-project"; import { useAppRouter } from "@/hooks/use-app-router"; import useLocalStorage from "@/hooks/use-local-storage"; -// assets -import emptyModule from "@/public/empty-state/module.svg"; +import type { Route } from "./+types/page"; -const ModuleIssuesPage = observer(() => { +function ModuleIssuesPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, projectId, moduleId } = useParams(); + const { workspaceSlug, projectId, moduleId } = params; // store hooks const { fetchModuleDetails, getModuleById } = useModule(); const { getProjectById } = useProject(); @@ -30,25 +30,19 @@ const ModuleIssuesPage = observer(() => { const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false"); const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false; // fetching module details - const { error } = useSWR( - workspaceSlug && projectId && moduleId ? `CURRENT_MODULE_DETAILS_${moduleId.toString()}` : null, - workspaceSlug && projectId && moduleId - ? () => fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString()) - : null + const { error } = useSWR(`CURRENT_MODULE_DETAILS_${moduleId}`, () => + fetchModuleDetails(workspaceSlug, projectId, moduleId) ); // derived values - const projectModule = moduleId ? getModuleById(moduleId.toString()) : undefined; - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const projectModule = getModuleById(moduleId); + const project = getProjectById(projectId); const pageTitle = project?.name && projectModule?.name ? `${project?.name} - ${projectModule?.name}` : undefined; const toggleSidebar = () => { setValue(`${!isSidebarCollapsed}`); }; - if (!workspaceSlug || !projectId || !moduleId) return <>; - // const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout; - return ( <> @@ -67,7 +61,7 @@ const ModuleIssuesPage = observer(() => {
- {moduleId && !isSidebarCollapsed && ( + {!isSidebarCollapsed && (
{ "0px 1px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px 0px rgba(16, 24, 40, 0.06), 0px 1px 8px -1px rgba(16, 24, 40, 0.06)", }} > - +
)}
)} ); -}); +} -export default ModuleIssuesPage; +export default observer(ModuleIssuesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx index e976c7353..4e8e0e60b 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx @@ -1,16 +1,19 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ModuleIssuesHeader } from "./header"; import { ModuleIssuesMobileHeader } from "./mobile-header"; -export default function ProjectModuleIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectModuleIssuesLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx index 269cf9455..781de0480 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx @@ -1,16 +1,19 @@ "use client"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { ModulesListHeader } from "./header"; import { ModulesListMobileHeader } from "./mobile-header"; -export default function ProjectModulesListLayout({ children }: { children: React.ReactNode }) { +export default function ProjectModulesListLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx index daef0c650..65b96d9ba 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx @@ -2,57 +2,63 @@ import { useCallback } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// types +import { useTheme } from "next-themes"; +// plane imports import { EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { TModuleFilters } from "@plane/types"; import { EUserProjectRoles } from "@plane/types"; -// components import { calculateTotalFilters } from "@plane/utils"; +// assets +import darkModulesAsset from "@/app/assets/empty-state/disabled-feature/modules-dark.webp?url"; +import lightModulesAsset from "@/app/assets/empty-state/disabled-feature/modules-light.webp?url"; +// components import { PageHead } from "@/components/core/page-title"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules"; -// helpers // hooks import { useModuleFilter } from "@/hooks/store/use-module-filter"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +import type { Route } from "./+types/page"; -const ProjectModulesPage = observer(() => { +function ProjectModulesPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // store const { getProjectById, currentProjectDetails } = useProject(); - const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } = - useModuleFilter(); + const { + currentProjectFilters = {}, + currentProjectDisplayFilters, + clearAllFilters, + updateFilters, + updateDisplayFilters, + } = useModuleFilter(); const { allowPermissions } = useUserPermissions(); // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name ? `${project?.name} - Modules` : undefined; const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/modules" }); + const resolvedPath = resolvedTheme === "light" ? lightModulesAsset : darkModulesAsset; const handleRemoveFilter = useCallback( (key: keyof TModuleFilters, value: string | null) => { - if (!projectId) return; - let newValues = currentProjectFilters?.[key] ?? []; + let newValues = currentProjectFilters[key] ?? []; if (!value) newValues = []; else newValues = newValues.filter((val) => val !== value); - updateFilters(projectId.toString(), { [key]: newValues }); + updateFilters(projectId, { [key]: newValues }); }, [currentProjectFilters, projectId, updateFilters] ); - if (!workspaceSlug || !projectId) return <>; - // No access to if (currentProjectDetails?.module_view === false) return ( @@ -76,16 +82,13 @@ const ProjectModulesPage = observer(() => { <>
- {(calculateTotalFilters(currentProjectFilters ?? {}) !== 0 || currentProjectDisplayFilters?.favorites) && ( + {(calculateTotalFilters(currentProjectFilters) !== 0 || currentProjectDisplayFilters?.favorites) && ( clearAllFilters(`${projectId}`)} + handleClearAllFilters={() => clearAllFilters(projectId)} handleRemoveFilter={handleRemoveFilter} - handleDisplayFiltersUpdate={(val) => { - if (!projectId) return; - updateDisplayFilters(projectId.toString(), val); - }} + handleDisplayFiltersUpdate={(val) => updateDisplayFilters(projectId, val)} alwaysAllowEditing /> )} @@ -93,6 +96,6 @@ const ProjectModulesPage = observer(() => {
); -}); +} -export default ProjectModulesPage; +export default observer(ProjectModulesPage); 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 4c15f2b22..6fdd3a1c4 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 @@ -3,7 +3,6 @@ import { useCallback, useEffect, useMemo } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { useParams } from "next/navigation"; import useSWR from "swr"; // plane types import { getButtonStyling } from "@plane/propel/button"; @@ -29,33 +28,34 @@ import { EPageStoreType, usePage, usePageStore } from "@/plane-web/hooks/store"; import { WorkspaceService } from "@/plane-web/services"; // services import { ProjectPageService, ProjectPageVersionService } from "@/services/page"; +import type { Route } from "./+types/page"; const workspaceService = new WorkspaceService(); const projectPageService = new ProjectPageService(); const projectPageVersionService = new ProjectPageVersionService(); const storeType = EPageStoreType.PROJECT; -const PageDetailsPage = observer(() => { +function PageDetailsPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, projectId, pageId } = useParams(); + const { workspaceSlug, projectId, pageId } = params; // store hooks const { createPage, fetchPageDetails } = usePageStore(storeType); const page = usePage({ - pageId: pageId?.toString() ?? "", + pageId, storeType, }); const { getWorkspaceBySlug } = useWorkspace(); const { uploadEditorAsset } = useEditorAsset(); // derived values - const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : ""; + const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug)?.id ?? "") : ""; const { canCurrentUserAccessPage, id, name, updateDescription } = page ?? {}; // entity search handler const fetchEntityCallback = useCallback( async (payload: TSearchEntityRequestPayload) => - await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { + await workspaceService.searchEntity(workspaceSlug, { ...payload, - project_id: projectId?.toString() ?? "", + project_id: projectId, }), [projectId, workspaceSlug] ); @@ -63,10 +63,8 @@ const PageDetailsPage = observer(() => { const { getEditorFileHandlers } = useEditorConfig(); // fetch page details const { error: pageDetailsError } = useSWR( - workspaceSlug && projectId && pageId ? `PAGE_DETAILS_${pageId}` : null, - workspaceSlug && projectId && pageId - ? () => fetchPageDetails(workspaceSlug?.toString(), projectId?.toString(), pageId.toString()) - : null, + `PAGE_DETAILS_${pageId}`, + () => fetchPageDetails(workspaceSlug, projectId, pageId), { revalidateIfStale: true, revalidateOnFocus: true, @@ -77,33 +75,17 @@ const PageDetailsPage = observer(() => { const pageRootHandlers: TPageRootHandlers = useMemo( () => ({ create: createPage, - fetchAllVersions: async (pageId) => { - if (!workspaceSlug || !projectId) return; - return await projectPageVersionService.fetchAllVersions(workspaceSlug.toString(), projectId.toString(), pageId); - }, + fetchAllVersions: async (pageId) => + await projectPageVersionService.fetchAllVersions(workspaceSlug, projectId, pageId), fetchDescriptionBinary: async () => { - if (!workspaceSlug || !projectId || !id) return; - return await projectPageService.fetchDescriptionBinary(workspaceSlug.toString(), projectId.toString(), id); + if (!id) return; + return await projectPageService.fetchDescriptionBinary(workspaceSlug, projectId, id); }, fetchEntity: fetchEntityCallback, - fetchVersionDetails: async (pageId, versionId) => { - if (!workspaceSlug || !projectId) return; - return await projectPageVersionService.fetchVersionById( - workspaceSlug.toString(), - projectId.toString(), - pageId, - versionId - ); - }, - restoreVersion: async (pageId, versionId) => { - if (!workspaceSlug || !projectId) return; - await projectPageVersionService.restoreVersion( - workspaceSlug.toString(), - projectId.toString(), - pageId, - versionId - ); - }, + fetchVersionDetails: async (pageId, versionId) => + await projectPageVersionService.fetchVersionById(workspaceSlug, projectId, pageId, versionId), + restoreVersion: async (pageId, versionId) => + await projectPageVersionService.restoreVersion(workspaceSlug, projectId, pageId, versionId), getRedirectionLink: (pageId) => { if (pageId) { return `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`; @@ -119,7 +101,7 @@ const PageDetailsPage = observer(() => { const pageRootConfig: TPageRootConfig = useMemo( () => ({ fileHandler: getEditorFileHandlers({ - projectId: projectId?.toString() ?? "", + projectId, uploadFile: async (blockId, file) => { const { asset_id } = await uploadEditorAsset({ blockId, @@ -128,13 +110,13 @@ const PageDetailsPage = observer(() => { entity_type: EFileAssetType.PAGE_DESCRIPTION, }, file, - projectId: projectId?.toString() ?? "", - workspaceSlug: workspaceSlug?.toString() ?? "", + projectId, + workspaceSlug, }); return asset_id; }, workspaceId, - workspaceSlug: workspaceSlug?.toString() ?? "", + workspaceSlug, }), }), [getEditorFileHandlers, id, uploadEditorAsset, projectId, workspaceId, workspaceSlug] @@ -143,8 +125,8 @@ const PageDetailsPage = observer(() => { const webhookConnectionParams: TWebhookConnectionQueryParams = useMemo( () => ({ documentType: "project_page", - projectId: projectId?.toString() ?? "", - workspaceSlug: workspaceSlug?.toString() ?? "", + projectId, + workspaceSlug, }), [projectId, workspaceSlug] ); @@ -178,7 +160,7 @@ const PageDetailsPage = observer(() => {
); - if (!page || !workspaceSlug || !projectId) return null; + if (!page) return null; return ( <> @@ -191,14 +173,14 @@ const PageDetailsPage = observer(() => { storeType={storeType} page={page} webhookConnectionParams={webhookConnectionParams} - workspaceSlug={workspaceSlug.toString()} - projectId={projectId?.toString()} + workspaceSlug={workspaceSlug} + projectId={projectId} /> ); -}); +} -export default PageDetailsPage; +export default observer(PageDetailsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx index a9147e0fe..a446165d0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx @@ -1,27 +1,27 @@ "use client"; // component -import { useParams } from "next/navigation"; +import { Outlet } from "react-router"; import useSWR from "swr"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // plane web hooks import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store"; // local components +import type { Route } from "./+types/layout"; import { PageDetailsHeader } from "./header"; -export default function ProjectPageDetailsLayout({ children }: { children: React.ReactNode }) { - const { workspaceSlug, projectId } = useParams(); +export default function ProjectPageDetailsLayout({ params }: Route.ComponentProps) { + const { workspaceSlug, projectId } = params; const { fetchPagesList } = usePageStore(EPageStoreType.PROJECT); // fetching pages list - useSWR( - workspaceSlug && projectId ? `PROJECT_PAGES_${projectId}` : null, - workspaceSlug && projectId ? () => fetchPagesList(workspaceSlug.toString(), projectId.toString()) : null - ); + useSWR(`PROJECT_PAGES_${projectId}`, () => fetchPagesList(workspaceSlug, projectId)); return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx index 6c62d42c3..64f4ee95d 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx @@ -1,17 +1,19 @@ "use client"; -import type { ReactNode } from "react"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local components import { PagesListHeader } from "./header"; -export default function ProjectPagesListLayout({ children }: { children: ReactNode }) { +export default function ProjectPagesListLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx index f5fb1f4ca..775472f82 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx @@ -1,12 +1,16 @@ "use client"; import { observer } from "mobx-react"; -import { useParams, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; +import { useTheme } from "next-themes"; // plane imports import { EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { TPageNavigationTabs } from "@plane/types"; import { EUserProjectRoles } from "@plane/types"; +// assets +import darkPagesAsset from "@/app/assets/empty-state/disabled-feature/pages-dark.webp?url"; +import lightPagesAsset from "@/app/assets/empty-state/disabled-feature/pages-light.webp?url"; // components import { PageHead } from "@/components/core/page-title"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; @@ -16,35 +20,35 @@ import { PagesListView } from "@/components/pages/pages-list-view"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; // plane web hooks import { EPageStoreType } from "@/plane-web/hooks/store"; +import type { Route } from "./+types/page"; -const ProjectPagesPage = observer(() => { +const getPageType = (pageType?: string | null): TPageNavigationTabs => { + if (pageType === "private") return "private"; + if (pageType === "archived") return "archived"; + return "public"; +}; + +function ProjectPagesPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); const searchParams = useSearchParams(); const type = searchParams.get("type"); - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // store hooks const { getProjectById, currentProjectDetails } = useProject(); const { allowPermissions } = useUserPermissions(); // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name ? `${project?.name} - Pages` : undefined; const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/pages" }); - - const currentPageType = (): TPageNavigationTabs => { - const pageType = type?.toString(); - if (pageType === "private") return "private"; - if (pageType === "archived") return "archived"; - return "public"; - }; - - if (!workspaceSlug || !projectId) return <>; + const resolvedPath = resolvedTheme === "light" ? lightPagesAsset : darkPagesAsset; + const pageType = getPageType(type); // No access to cycle if (currentProjectDetails?.page_view === false) @@ -68,15 +72,15 @@ const ProjectPagesPage = observer(() => { <> - + ); -}); +} -export default ProjectPagesPage; +export default observer(ProjectPagesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx index a5342d81a..4d3c3a183 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx @@ -1,8 +1,9 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import useSWR from "swr"; +// assets +import emptyView from "@/app/assets/empty-state/view.svg?url"; // components import { EmptyState } from "@/components/common/empty-state"; import { PageHead } from "@/components/core/page-title"; @@ -10,28 +11,22 @@ import { ProjectViewLayoutRoot } from "@/components/issues/issue-layouts/roots/p // hooks import { useProject } from "@/hooks/store/use-project"; import { useProjectView } from "@/hooks/store/use-project-view"; -// assets import { useAppRouter } from "@/hooks/use-app-router"; -import emptyView from "@/public/empty-state/view.svg"; +import type { Route } from "./+types/page"; -const ProjectViewIssuesPage = observer(() => { +function ProjectViewIssuesPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, projectId, viewId } = useParams(); + const { workspaceSlug, projectId, viewId } = params; // store hooks const { fetchViewDetails, getViewById } = useProjectView(); const { getProjectById } = useProject(); // derived values - const projectView = viewId ? getViewById(viewId.toString()) : undefined; - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const projectView = getViewById(viewId); + const project = getProjectById(projectId); const pageTitle = project?.name && projectView?.name ? `${project?.name} - ${projectView?.name}` : undefined; - const { error } = useSWR( - workspaceSlug && projectId && viewId ? `VIEW_DETAILS_${viewId.toString()}` : null, - workspaceSlug && projectId && viewId - ? () => fetchViewDetails(workspaceSlug.toString(), projectId.toString(), viewId.toString()) - : null - ); + const { error } = useSWR(`VIEW_DETAILS_${viewId}`, () => fetchViewDetails(workspaceSlug, projectId, viewId)); if (error) { return ( @@ -53,6 +48,6 @@ const ProjectViewIssuesPage = observer(() => { ); -}); +} -export default ProjectViewIssuesPage; +export default observer(ProjectViewIssuesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx index af6e57d49..f7ad8f212 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx @@ -1,15 +1,18 @@ "use client"; +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local components import { ProjectViewIssuesHeader } from "./[viewId]/header"; -export default function ProjectViewIssuesLayout({ children }: { children: React.ReactNode }) { +export default function ProjectViewIssuesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx index d96c8256a..c6fc4039c 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx @@ -1,16 +1,19 @@ "use client"; +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local components import { ProjectViewsHeader } from "./header"; import { ViewMobileHeader } from "./mobile-header"; -export default function ProjectViewsListLayout({ children }: { children: React.ReactNode }) { +export default function ProjectViewsListLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx index 62de1c0d5..b68a395bd 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx @@ -2,31 +2,35 @@ import { useCallback } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; -// components +import { useTheme } from "next-themes"; +// plane imports import { EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { EViewAccess, TViewFilterProps } from "@plane/types"; import { EUserProjectRoles } from "@plane/types"; import { Header, EHeaderVariant } from "@plane/ui"; import { calculateTotalFilters } from "@plane/utils"; +// assets +import darkViewsAsset from "@/app/assets/empty-state/disabled-feature/views-dark.webp?url"; +import lightViewsAsset from "@/app/assets/empty-state/disabled-feature/views-light.webp?url"; +// components import { PageHead } from "@/components/core/page-title"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { ViewAppliedFiltersList } from "@/components/views/applied-filters"; import { ProjectViewsList } from "@/components/views/views-list"; -// constants -// helpers // hooks import { useProject } from "@/hooks/store/use-project"; import { useProjectView } from "@/hooks/store/use-project-view"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +import type { Route } from "./+types/page"; -const ProjectViewsPage = observer(() => { +function ProjectViewsPage({ params }: Route.ComponentProps) { // router const router = useAppRouter(); - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // store @@ -34,10 +38,10 @@ const ProjectViewsPage = observer(() => { const { filters, updateFilters, clearAllFilters } = useProjectView(); const { allowPermissions } = useUserPermissions(); // derived values - const project = projectId ? getProjectById(projectId.toString()) : undefined; + const project = getProjectById(projectId); const pageTitle = project?.name ? `${project?.name} - Views` : undefined; const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/views" }); + const resolvedPath = resolvedTheme === "light" ? lightViewsAsset : darkViewsAsset; const handleRemoveFilter = useCallback( (key: keyof TViewFilterProps, value: string | EViewAccess | null) => { @@ -58,8 +62,6 @@ const ProjectViewsPage = observer(() => { const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0; - if (!workspaceSlug || !projectId) return <>; - // No access to if (currentProjectDetails?.issue_views_view === false) return ( @@ -95,6 +97,6 @@ const ProjectViewsPage = observer(() => { ); -}); +} -export default ProjectViewsPage; +export default observer(ProjectViewsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx index d33616ed5..c2c678c77 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx @@ -1,17 +1,20 @@ "use client"; -import type { ReactNode } from "react"; // components +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local components import { ProjectsListHeader } from "@/plane-web/components/projects/header"; import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; -export default function ProjectListLayout({ children }: { children: ReactNode }) { + +export default function ProjectListLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx index 25cefaf56..4053aa6c7 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx @@ -1,18 +1,16 @@ "use client"; -import type { ReactNode } from "react"; -import { useParams } from "next/navigation"; +import { Outlet } from "react-router"; // plane web layouts import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper"; +import type { Route } from "./+types/layout"; -const ProjectDetailLayout = ({ children }: { children: ReactNode }) => { +export default function ProjectDetailLayout({ params }: Route.ComponentProps) { // router - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; return ( - - {children} + + ); -}; - -export default ProjectDetailLayout; +} diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(list)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(list)/layout.tsx index d33616ed5..4e9249ed7 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(list)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(list)/layout.tsx @@ -1,17 +1,20 @@ "use client"; -import type { ReactNode } from "react"; +import { Outlet } from "react-router"; // components import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; // local components import { ProjectsListHeader } from "@/plane-web/components/projects/header"; import { ProjectsListMobileHeader } from "@/plane-web/components/projects/mobile-header"; -export default function ProjectListLayout({ children }: { children: ReactNode }) { + +export default function ProjectListLayout() { return ( <> } mobileHeader={} /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/star-us-link.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/star-us-link.tsx index 1573e7529..f65ea3b73 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/star-us-link.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/star-us-link.tsx @@ -5,11 +5,11 @@ import { useTheme } from "next-themes"; // plane imports import { HEADER_GITHUB_ICON, GITHUB_REDIRECTED_TRACKER_EVENT } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; +// assets +import githubBlackImage from "@/app/assets/logos/github-black.png?url"; +import githubWhiteImage from "@/app/assets/logos/github-white.png?url"; // helpers import { captureElementAndEvent } from "@/helpers/event-tracker.helper"; -// public imports -import githubBlackImage from "@/public/logos/github-black.png"; -import githubWhiteImage from "@/public/logos/github-white.png"; export const StarUsOnGitHubLink = () => { // plane hooks diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/stickies/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/stickies/layout.tsx index d2abcd3f6..366a71ab7 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/stickies/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/stickies/layout.tsx @@ -1,14 +1,17 @@ "use client"; +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { WorkspaceStickyHeader } from "./header"; -export default function WorkspaceStickiesLayout({ children }: { children: React.ReactNode }) { +export default function WorkspaceStickiesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx index 9419f2b31..7f2c4abe9 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // plane imports import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants"; // components @@ -10,10 +9,11 @@ import { PageHead } from "@/components/core/page-title"; import { AllIssueLayoutRoot } from "@/components/issues/issue-layouts/roots/all-issue-layout-root"; // hooks import { useWorkspace } from "@/hooks/store/use-workspace"; +import type { Route } from "./+types/page"; -const GlobalViewIssuesPage = observer(() => { +function GlobalViewIssuesPage({ params }: Route.ComponentProps) { // router - const { globalViewId } = useParams(); + const { globalViewId } = params; // store hooks const { currentWorkspace } = useWorkspace(); // states @@ -31,6 +31,6 @@ const GlobalViewIssuesPage = observer(() => { ); -}); +} -export default GlobalViewIssuesPage; +export default observer(GlobalViewIssuesPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/layout.tsx index 6f3dbe1b9..95469c3d0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/layout.tsx @@ -1,14 +1,17 @@ "use client"; +import { Outlet } from "react-router"; import { AppHeader } from "@/components/core/app-header"; import { ContentWrapper } from "@/components/core/content-wrapper"; import { GlobalIssuesHeader } from "./header"; -export default function GlobalIssuesLayout({ children }: { children: React.ReactNode }) { +export default function GlobalIssuesLayout() { return ( <> } /> - {children} + + + ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/page.tsx index 5a3969a22..9f8865077 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/page.tsx @@ -14,7 +14,7 @@ import { GlobalViewsList } from "@/components/workspace/views/views-list"; // hooks import { useWorkspace } from "@/hooks/store/use-workspace"; -const WorkspaceViewsPage = observer(() => { +function WorkspaceViewsPage() { const [query, setQuery] = useState(""); // store const { currentWorkspace } = useWorkspace(); @@ -47,6 +47,6 @@ const WorkspaceViewsPage = observer(() => { ); -}); +} -export default WorkspaceViewsPage; +export default observer(WorkspaceViewsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/layout.tsx index a42ae9cf8..ea1745d75 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/layout.tsx @@ -1,12 +1,14 @@ "use client"; +import { Outlet } from "react-router"; +// components import { ContentWrapper } from "@/components/core/content-wrapper"; import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider"; import { SettingsHeader } from "@/components/settings/header"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; import { WorkspaceAuthWrapper } from "@/plane-web/layouts/workspace-wrapper"; -export default function SettingsLayout({ children }: { children: React.ReactNode }) { +export default function SettingsLayout() { return ( @@ -17,7 +19,9 @@ export default function SettingsLayout({ children }: { children: React.ReactNode {/* Content */} -
{children}
+
+ +
diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/billing/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/billing/page.tsx index 6c8950b4c..6eeb9e667 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/billing/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/billing/page.tsx @@ -12,7 +12,7 @@ import { useUserPermissions } from "@/hooks/store/user"; // plane web components import { BillingRoot } from "@/plane-web/components/workspace/billing"; -const BillingSettingsPage = observer(() => { +function BillingSettingsPage() { // store hooks const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { currentWorkspace } = useWorkspace(); @@ -30,6 +30,6 @@ const BillingSettingsPage = observer(() => { ); -}); +} -export default BillingSettingsPage; +export default observer(BillingSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/exports/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/exports/page.tsx index b0ba8774a..e637dfb4f 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/exports/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/exports/page.tsx @@ -15,7 +15,7 @@ import SettingsHeading from "@/components/settings/heading"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUserPermissions } from "@/hooks/store/user"; -const ExportsPage = observer(() => { +function ExportsPage() { // store hooks const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { currentWorkspace } = useWorkspace(); @@ -51,6 +51,6 @@ const ExportsPage = observer(() => { ); -}); +} -export default ExportsPage; +export default observer(ExportsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/imports/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/imports/page.tsx index 2838e2226..38585a77e 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/imports/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/imports/page.tsx @@ -12,7 +12,7 @@ import { SettingsHeading } from "@/components/settings/heading"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUserPermissions } from "@/hooks/store/user"; -const ImportsPage = observer(() => { +function ImportsPage() { // router // store hooks const { currentWorkspace } = useWorkspace(); @@ -32,6 +32,6 @@ const ImportsPage = observer(() => { ); -}); +} -export default ImportsPage; +export default observer(ImportsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/integrations/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/integrations/page.tsx index bb9d8f3d3..a538a4486 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/integrations/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/integrations/page.tsx @@ -1,6 +1,5 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import useSWR from "swr"; // components import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; @@ -20,9 +19,7 @@ import { IntegrationService } from "@/services/integrations"; const integrationService = new IntegrationService(); -const WorkspaceIntegrationsPage = observer(() => { - // router - const { workspaceSlug } = useParams(); +function WorkspaceIntegrationsPage() { // store hooks const { currentWorkspace } = useWorkspace(); const { allowPermissions } = useUserPermissions(); @@ -30,8 +27,8 @@ const WorkspaceIntegrationsPage = observer(() => { // derived values const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Integrations` : undefined; - const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () => - workspaceSlug && isAdmin ? integrationService.getAppIntegrationsList() : null + const { data: appIntegrations } = useSWR(isAdmin ? APP_INTEGRATIONS : null, () => + isAdmin ? integrationService.getAppIntegrationsList() : null ); if (!isAdmin) return ; @@ -53,6 +50,6 @@ const WorkspaceIntegrationsPage = observer(() => { ); -}); +} -export default WorkspaceIntegrationsPage; +export default observer(WorkspaceIntegrationsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/layout.tsx index 385fa6548..08ed35d80 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/layout.tsx @@ -1,8 +1,8 @@ "use client"; -import type { FC, ReactNode } from "react"; import { observer } from "mobx-react"; import { usePathname } from "next/navigation"; +import { Outlet } from "react-router"; // constants import { WORKSPACE_SETTINGS_ACCESS } from "@plane/constants"; import type { EUserWorkspaceRoles } from "@plane/types"; @@ -15,12 +15,7 @@ import { useUserPermissions } from "@/hooks/store/user"; // local components import { WorkspaceSettingsSidebar } from "./sidebar"; -export interface IWorkspaceSettingLayout { - children: ReactNode; -} - -const WorkspaceSettingLayout: FC = observer((props) => { - const { children } = props; +function WorkspaceSettingLayout() { // store hooks const { workspaceUserInfo, getWorkspaceRoleByWorkspaceSlug } = useUserPermissions(); // next hooks @@ -46,12 +41,14 @@ const WorkspaceSettingLayout: FC = observer((props) => ) : (
{}
-
{children}
+
+ +
)} ); -}); +} -export default WorkspaceSettingLayout; +export default observer(WorkspaceSettingLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx index 80871f19e..20d3f57ee 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { Search } from "lucide-react"; // types import { @@ -32,13 +31,14 @@ import { useUserPermissions } from "@/hooks/store/user"; // plane web components import { BillingActionsButton } from "@/plane-web/components/workspace/billing/billing-actions-button"; import { SendWorkspaceInvitationModal } from "@/plane-web/components/workspace/members/invite-modal"; +import type { Route } from "./+types/page"; -const WorkspaceMembersSettingsPage = observer(() => { +function WorkspaceMembersSettingsPage({ params }: Route.ComponentProps) { // states const [inviteModal, setInviteModal] = useState(false); const [searchQuery, setSearchQuery] = useState(""); // router - const { workspaceSlug } = useParams(); + const { workspaceSlug } = params; // store hooks const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { @@ -54,39 +54,42 @@ const WorkspaceMembersSettingsPage = observer(() => { EUserPermissionsLevel.WORKSPACE ); - const handleWorkspaceInvite = (data: IWorkspaceBulkInviteFormData) => { - if (!workspaceSlug) return; + const handleWorkspaceInvite = async (data: IWorkspaceBulkInviteFormData) => { + try { + await inviteMembersToWorkspace(workspaceSlug, data); - return inviteMembersToWorkspace(workspaceSlug.toString(), data) - .then(() => { - setInviteModal(false); - captureSuccess({ - eventName: MEMBER_TRACKER_EVENTS.invite, - payload: { - emails: [...data.emails.map((email) => email.email)], - }, - }); - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: t("workspace_settings.settings.members.invitations_sent_successfully"), - }); - }) - .catch((err) => { - captureError({ - eventName: MEMBER_TRACKER_EVENTS.invite, - payload: { - emails: [...data.emails.map((email) => email.email)], - }, - error: err, - }); - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: `${err.error ?? t("something_went_wrong_please_try_again")}`, - }); - throw err; + setInviteModal(false); + + captureSuccess({ + eventName: MEMBER_TRACKER_EVENTS.invite, + payload: { + emails: data.emails.map((email) => email.email), + }, }); + + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: t("workspace_settings.settings.members.invitations_sent_successfully"), + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + captureError({ + eventName: MEMBER_TRACKER_EVENTS.invite, + payload: { + emails: data.emails.map((email) => email.email), + }, + error: err, + }); + + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: `${err.error ?? t("something_went_wrong_please_try_again")}`, + }); + + throw err; + } }; // Handler for role filter updates @@ -162,6 +165,6 @@ const WorkspaceMembersSettingsPage = observer(() => { ); -}); +} -export default WorkspaceMembersSettingsPage; +export default observer(WorkspaceMembersSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/page.tsx index 12fcdca8f..9f27cb7d0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/page.tsx @@ -10,7 +10,7 @@ import { WorkspaceDetails } from "@/components/workspace/settings/workspace-deta // hooks import { useWorkspace } from "@/hooks/store/use-workspace"; -const WorkspaceSettingsPage = observer(() => { +function WorkspaceSettingsPage() { // store hooks const { currentWorkspace } = useWorkspace(); const { t } = useTranslation(); @@ -25,6 +25,6 @@ const WorkspaceSettingsPage = observer(() => { ); -}); +} -export default WorkspaceSettingsPage; +export default observer(WorkspaceSettingsPage); 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 7605f2276..f8801d1ca 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 @@ -2,7 +2,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import useSWR from "swr"; import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_EVENTS } from "@plane/constants"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; @@ -18,12 +17,13 @@ import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { useWebhook } from "@/hooks/store/use-webhook"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUserPermissions } from "@/hooks/store/user"; +import type { Route } from "./+types/page"; -const WebhookDetailsPage = observer(() => { +function WebhookDetailsPage({ params }: Route.ComponentProps) { // states const [deleteWebhookModal, setDeleteWebhookModal] = useState(false); // router - const { workspaceSlug, webhookId } = useParams(); + const { workspaceSlug, webhookId } = params; // mobx store const { currentWebhook, fetchWebhookById, updateWebhook } = useWebhook(); const { currentWorkspace } = useWorkspace(); @@ -33,57 +33,55 @@ const WebhookDetailsPage = observer(() => { // useEffect(() => { // if (isCreated !== "true") clearSecretKey(); // }, [clearSecretKey, isCreated]); - // derived values const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhook` : undefined; useSWR( - workspaceSlug && webhookId && isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null, - workspaceSlug && webhookId && isAdmin - ? () => fetchWebhookById(workspaceSlug.toString(), webhookId.toString()) - : null + isAdmin ? `WEBHOOK_DETAILS_${workspaceSlug}_${webhookId}` : null, + isAdmin ? () => fetchWebhookById(workspaceSlug, webhookId) : null ); const handleUpdateWebhook = async (formData: IWebhook) => { - if (!workspaceSlug || !formData || !formData.id) return; + if (!formData || !formData.id) return; + const payload = { - url: formData?.url, - is_active: formData?.is_active, - project: formData?.project, - cycle: formData?.cycle, - module: formData?.module, - issue: formData?.issue, - issue_comment: formData?.issue_comment, + url: formData.url, + is_active: formData.is_active, + project: formData.project, + cycle: formData.cycle, + module: formData.module, + issue: formData.issue, + issue_comment: formData.issue_comment, }; - await updateWebhook(workspaceSlug.toString(), formData.id, payload) - .then(() => { - captureSuccess({ - eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, - payload: { - webhook: formData.id, - }, - }); - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Webhook updated successfully.", - }); - }) - .catch((error) => { - captureError({ - eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, - payload: { - webhook: formData.id, - }, - error: error as Error, - }); - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error?.error ?? "Something went wrong. Please try again.", - }); + + try { + await updateWebhook(workspaceSlug, formData.id, payload); + + captureSuccess({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, + payload: { webhook: formData.id }, }); + + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Webhook updated successfully.", + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + captureError({ + eventName: WORKSPACE_SETTINGS_TRACKER_EVENTS.webhook_updated, + payload: { webhook: formData.id }, + error: error as Error, + }); + + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: error?.error ?? "Something went wrong. Please try again.", + }); + } }; if (!isAdmin) @@ -108,13 +106,13 @@ const WebhookDetailsPage = observer(() => { setDeleteWebhookModal(false)} />
-
- await handleUpdateWebhook(data)} data={currentWebhook} /> +
+
{currentWebhook && setDeleteWebhookModal(true)} />}
); -}); +} -export default WebhookDetailsPage; +export default observer(WebhookDetailsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx index bfcfbd444..eff5ce4d4 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx @@ -1,8 +1,7 @@ "use client"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import useSWR from "swr"; // plane imports import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; @@ -20,12 +19,13 @@ import { captureClick } from "@/helpers/event-tracker.helper"; import { useWebhook } from "@/hooks/store/use-webhook"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUserPermissions } from "@/hooks/store/user"; +import type { Route } from "./+types/page"; -const WebhooksListPage = observer(() => { +function WebhooksListPage({ params }: Route.ComponentProps) { // states const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false); // router - const { workspaceSlug } = useParams(); + const { workspaceSlug } = params; // plane hooks const { t } = useTranslation(); // mobx store @@ -36,8 +36,8 @@ const WebhooksListPage = observer(() => { const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); useSWR( - workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null, - workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null + canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null, + canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug) : null ); const pageTitle = currentWorkspace?.name @@ -112,6 +112,6 @@ const WebhooksListPage = observer(() => {
); -}); +} -export default WebhooksListPage; +export default observer(WebhooksListPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx index 799c29e17..e76dcb1aa 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx @@ -2,29 +2,34 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; import { useTranslation } from "@plane/i18n"; // ui import { Button } from "@plane/propel/button"; +// assets +import darkActivityAsset from "@/app/assets/empty-state/profile/activity-dark.webp?url"; +import lightActivityAsset from "@/app/assets/empty-state/profile/activity-light.webp?url"; // components import { PageHead } from "@/components/core/page-title"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { ProfileActivityListPage } from "@/components/profile/activity/profile-activity-list"; // hooks import { SettingsHeading } from "@/components/settings/heading"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const PER_PAGE = 100; -const ProfileActivityPage = observer(() => { +function ProfileActivityPage() { // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); const [resultsCount, setResultsCount] = useState(0); const [isEmpty, setIsEmpty] = useState(false); + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // derived values - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" }); + const resolvedPath = resolvedTheme === "light" ? lightActivityAsset : darkActivityAsset; const updateTotalPages = (count: number) => setTotalPages(count); @@ -84,6 +89,6 @@ const ProfileActivityPage = observer(() => { )} ); -}); +} -export default ProfileActivityPage; +export default observer(ProfileActivityPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx index 5e9c17a17..7b0663da9 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx @@ -21,7 +21,7 @@ import { useWorkspace } from "@/hooks/store/use-workspace"; const apiTokenService = new APITokenService(); -const ApiTokensPage = observer(() => { +function ApiTokensPage() { // states const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false); // router @@ -106,6 +106,6 @@ const ApiTokensPage = observer(() => { ); -}); +} -export default ApiTokensPage; +export default observer(ApiTokensPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/layout.tsx index 61f227f46..0678b884a 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/layout.tsx @@ -1,8 +1,8 @@ "use client"; -import type { ReactNode } from "react"; import { observer } from "mobx-react"; import { usePathname } from "next/navigation"; +import { Outlet } from "react-router"; // components import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { getProfileActivePath } from "@/components/settings/helper"; @@ -10,12 +10,7 @@ import { SettingsMobileNav } from "@/components/settings/mobile"; // local imports import { ProfileSidebar } from "./sidebar"; -type Props = { - children: ReactNode; -}; - -const ProfileSettingsLayout = observer((props: Props) => { - const { children } = props; +function ProfileSettingsLayout() { // router const pathname = usePathname(); @@ -27,11 +22,13 @@ const ProfileSettingsLayout = observer((props: Props) => {
- {children} + + +
); -}); +} -export default ProfileSettingsLayout; +export default observer(ProfileSettingsLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx index 238119334..0fef26e1a 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx @@ -9,7 +9,7 @@ import { ProfileForm } from "@/components/profile/form"; // hooks import { useUser } from "@/hooks/store/user"; -const ProfileSettingsPage = observer(() => { +function ProfileSettingsPage() { const { t } = useTranslation(); // store hooks const { data: currentUser, userProfile } = useUser(); @@ -27,6 +27,6 @@ const ProfileSettingsPage = observer(() => { ); -}); +} -export default ProfileSettingsPage; +export default observer(ProfileSettingsPage); 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 5032ae1ba..4ea14f70b 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 @@ -13,7 +13,7 @@ import { SettingsHeading } from "@/components/settings/heading"; // hooks import { useUserProfile } from "@/hooks/store/user"; -const ProfileAppearancePage = observer(() => { +function ProfileAppearancePage() { const { t } = useTranslation(); // hooks const { data: userProfile } = useUserProfile(); @@ -44,6 +44,6 @@ const ProfileAppearancePage = observer(() => { )} ); -}); +} -export default ProfileAppearancePage; +export default observer(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 b4815a69f..fad026dda 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 @@ -42,7 +42,7 @@ const defaultShowPassword = { confirmPassword: false, }; -const SecurityPage = observer(() => { +function SecurityPage() { // store const { data: currentUser, changePassword } = useUser(); // states @@ -255,6 +255,6 @@ const SecurityPage = observer(() => { ); -}); +} -export default SecurityPage; +export default observer(SecurityPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/layout.tsx new file mode 100644 index 000000000..8e62377f3 --- /dev/null +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/layout.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { observer } from "mobx-react"; +import { Outlet } from "react-router"; +// plane web imports +import { AutomationsListWrapper } from "@/plane-web/components/automations/list/wrapper"; +import type { Route } from "./+types/layout"; + +function AutomationsListLayout({ params }: Route.ComponentProps) { + const { projectId, workspaceSlug } = params; + + return ( + + + + ); +} + +export default observer(AutomationsListLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx index 0d9de2f2a..2e2664ff4 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx @@ -1,8 +1,6 @@ "use client"; -import React from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; @@ -19,12 +17,11 @@ import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; // plane web imports import { CustomAutomationsRoot } from "@/plane-web/components/automations/root"; +import type { Route } from "./+types/page"; -const AutomationSettingsPage = observer(() => { +function AutomationSettingsPage({ params }: Route.ComponentProps) { // router - const { workspaceSlug: workspaceSlugParam, projectId: projectIdParam } = useParams(); - const workspaceSlug = workspaceSlugParam?.toString(); - const projectId = projectIdParam?.toString(); + const { workspaceSlug, projectId } = params; // store hooks const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { currentProjectDetails: projectDetails, updateProject } = useProject(); @@ -35,15 +32,17 @@ const AutomationSettingsPage = observer(() => { const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); const handleChange = async (formData: Partial) => { - if (!workspaceSlug || !projectId || !projectDetails) return; + if (!projectDetails) return; - await updateProject(workspaceSlug.toString(), projectId.toString(), formData).catch(() => { + try { + await updateProject(workspaceSlug, projectId, formData); + } catch { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }); - }); + } }; // derived values @@ -67,6 +66,6 @@ const AutomationSettingsPage = observer(() => { ); -}); +} -export default AutomationSettingsPage; +export default observer(AutomationSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx index bc448aedb..bbf1cd313 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // components import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view"; @@ -11,9 +10,10 @@ import { EstimateRoot } from "@/components/estimates"; import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; +import type { Route } from "./+types/page"; -const EstimatesSettingsPage = observer(() => { - const { workspaceSlug, projectId } = useParams(); +function EstimatesSettingsPage({ params }: Route.ComponentProps) { + const { workspaceSlug, projectId } = params; // store const { currentProjectDetails } = useProject(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); @@ -22,8 +22,6 @@ const EstimatesSettingsPage = observer(() => { const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Estimates` : undefined; const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); - if (!workspaceSlug || !projectId) return <>; - if (workspaceUserInfo && !canPerformProjectAdminActions) { return ; } @@ -32,14 +30,10 @@ const EstimatesSettingsPage = observer(() => {
- +
); -}); +} -export default EstimatesSettingsPage; +export default observer(EstimatesSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx index 730177e13..dbcb2c5aa 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // components import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view"; @@ -11,9 +10,10 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { ProjectFeaturesList } from "@/plane-web/components/projects/settings/features-list"; +import type { Route } from "./+types/page"; -const FeaturesSettingsPage = observer(() => { - const { workspaceSlug, projectId } = useParams(); +function FeaturesSettingsPage({ params }: Route.ComponentProps) { + const { workspaceSlug, projectId } = params; // store const { workspaceUserInfo, allowPermissions } = useUserPermissions(); @@ -22,8 +22,6 @@ const FeaturesSettingsPage = observer(() => { const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Features` : undefined; const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); - if (!workspaceSlug || !projectId) return null; - if (workspaceUserInfo && !canPerformProjectAdminActions) { return ; } @@ -33,13 +31,13 @@ const FeaturesSettingsPage = observer(() => {
); -}); +} -export default FeaturesSettingsPage; +export default observer(FeaturesSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx index 7579d6b5a..acfff800d 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx @@ -14,7 +14,7 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; -const LabelsSettingsPage = observer(() => { +function LabelsSettingsPage() { // store hooks const { currentProjectDetails } = useProject(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); @@ -54,6 +54,6 @@ const LabelsSettingsPage = observer(() => { ); -}); +} -export default LabelsSettingsPage; +export default observer(LabelsSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx index 545903ea2..d12bc65a2 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; // plane imports import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; @@ -18,18 +17,17 @@ import { useUserPermissions } from "@/hooks/store/user"; // plane web imports import { ProjectTeamspaceList } from "@/plane-web/components/projects/teamspaces/teamspace-list"; import { getProjectSettingsPageLabelI18nKey } from "@/plane-web/helpers/project-settings"; +import type { Route } from "./+types/page"; -const MembersSettingsPage = observer(() => { +function MembersSettingsPage({ params }: Route.ComponentProps) { // router - const { workspaceSlug: routerWorkspaceSlug, projectId: routerProjectId } = useParams(); + const { workspaceSlug, projectId } = params; // plane hooks const { t } = useTranslation(); // store hooks const { currentProjectDetails } = useProject(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); // derived values - const projectId = routerProjectId?.toString(); - const workspaceSlug = routerWorkspaceSlug?.toString(); const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Members` : undefined; const isProjectMemberOrAdmin = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -51,6 +49,6 @@ const MembersSettingsPage = observer(() => { ); -}); +} -export default MembersSettingsPage; +export default observer(MembersSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx index 00899909c..1d67c80af 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import useSWR from "swr"; // plane imports import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; @@ -18,41 +17,34 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; // hooks import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; +import type { Route } from "./+types/page"; -const ProjectSettingsPage = observer(() => { +function ProjectSettingsPage({ params }: Route.ComponentProps) { // states const [selectProject, setSelectedProject] = useState(null); const [archiveProject, setArchiveProject] = useState(false); // router - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; // store hooks const { currentProjectDetails, fetchProjectDetails } = useProject(); const { allowPermissions } = useUserPermissions(); // api call to fetch project details // TODO: removed this API if not necessary - const { isLoading } = useSWR( - workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null, - workspaceSlug && projectId ? () => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()) : null - ); + const { isLoading } = useSWR(`PROJECT_DETAILS_${projectId}`, () => fetchProjectDetails(workspaceSlug, projectId)); // derived values - const isAdmin = allowPermissions( - [EUserPermissions.ADMIN], - EUserPermissionsLevel.PROJECT, - workspaceSlug.toString(), - projectId.toString() - ); + const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId); const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined; return ( - {currentProjectDetails && workspaceSlug && projectId && ( + {currentProjectDetails && ( <> setArchiveProject(false)} archive @@ -66,11 +58,11 @@ const ProjectSettingsPage = observer(() => { )}
- {currentProjectDetails && workspaceSlug && projectId && !isLoading ? ( + {currentProjectDetails && !isLoading ? ( ) : ( @@ -92,6 +84,6 @@ const ProjectSettingsPage = observer(() => {
); -}); +} -export default ProjectSettingsPage; +export default observer(ProjectSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx index 338319e47..c85c643dd 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx @@ -1,7 +1,6 @@ "use client"; import { observer } from "mobx-react"; -import { useParams } from "next/navigation"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // components @@ -13,9 +12,10 @@ import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { SettingsHeading } from "@/components/settings/heading"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; +import type { Route } from "./+types/page"; -const StatesSettingsPage = observer(() => { - const { workspaceSlug, projectId } = useParams(); +function StatesSettingsPage({ params }: Route.ComponentProps) { + const { workspaceSlug, projectId } = params; // store const { currentProjectDetails } = useProject(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); @@ -42,12 +42,10 @@ const StatesSettingsPage = observer(() => { title={t("project_settings.states.heading")} description={t("project_settings.states.description")} /> - {workspaceSlug && projectId && ( - - )} + ); -}); +} -export default StatesSettingsPage; +export default observer(StatesSettingsPage); diff --git a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/layout.tsx index e82348c69..2adbe9989 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/layout.tsx @@ -1,9 +1,9 @@ "use client"; -import type { ReactNode } from "react"; import { useEffect } from "react"; import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; +import { usePathname } from "next/navigation"; +import { Outlet } from "react-router"; // components import { getProjectActivePath } from "@/components/settings/helper"; import { SettingsMobileNav } from "@/components/settings/mobile"; @@ -11,17 +11,13 @@ import { ProjectSettingsSidebar } from "@/components/settings/project/sidebar"; import { useProject } from "@/hooks/store/use-project"; import { useAppRouter } from "@/hooks/use-app-router"; import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper"; +import type { Route } from "./+types/layout"; -type Props = { - children: ReactNode; -}; - -const ProjectSettingsLayout = observer((props: Props) => { - const { children } = props; +function ProjectSettingsLayout({ params }: Route.ComponentProps) { // router const router = useAppRouter(); const pathname = usePathname(); - const { workspaceSlug, projectId } = useParams(); + const { workspaceSlug, projectId } = params; const { joinedProjectIds } = useProject(); useEffect(() => { @@ -34,14 +30,16 @@ const ProjectSettingsLayout = observer((props: Props) => { return ( <> - +
{projectId && }
-
{children}
+
+ +
); -}); +} -export default ProjectSettingsLayout; +export default observer(ProjectSettingsLayout); diff --git a/apps/web/app/(all)/[workspaceSlug]/layout.tsx b/apps/web/app/(all)/[workspaceSlug]/layout.tsx index ed16556a9..22f1be8a9 100644 --- a/apps/web/app/(all)/[workspaceSlug]/layout.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/layout.tsx @@ -1,12 +1,15 @@ "use client"; +import { Outlet } from "react-router"; import { AppRailProvider } from "@/hooks/context/app-rail-context"; import { WorkspaceContentWrapper } from "@/plane-web/components/workspace/content-wrapper"; -export default function WorkspaceLayout({ children }: { children: React.ReactNode }) { +export default function WorkspaceLayout() { return ( - {children} + + + ); } diff --git a/apps/web/app/(all)/accounts/forgot-password/layout.tsx b/apps/web/app/(all)/accounts/forgot-password/layout.tsx index eb7439541..0e4b9b4be 100644 --- a/apps/web/app/(all)/accounts/forgot-password/layout.tsx +++ b/apps/web/app/(all)/accounts/forgot-password/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Forgot Password - Plane", -}; - -export default function ForgotPasswordLayout({ children }: { children: React.ReactNode }) { - return children; +export default function ForgotPasswordLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Forgot Password - Plane" }]; diff --git a/apps/web/app/(all)/accounts/forgot-password/page.tsx b/apps/web/app/(all)/accounts/forgot-password/page.tsx index c49e21748..7c121e25f 100644 --- a/apps/web/app/(all)/accounts/forgot-password/page.tsx +++ b/apps/web/app/(all)/accounts/forgot-password/page.tsx @@ -10,15 +10,17 @@ import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; import DefaultLayout from "@/layouts/default-layout"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; -const ForgotPasswordPage = observer(() => ( - - -
- - -
-
-
-)); +function ForgotPasswordPage() { + return ( + + +
+ + +
+
+
+ ); +} -export default ForgotPasswordPage; +export default observer(ForgotPasswordPage); diff --git a/apps/web/app/(all)/accounts/reset-password/layout.tsx b/apps/web/app/(all)/accounts/reset-password/layout.tsx index 54488aa38..30e798d44 100644 --- a/apps/web/app/(all)/accounts/reset-password/layout.tsx +++ b/apps/web/app/(all)/accounts/reset-password/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Reset Password - Plane", -}; - -export default function ResetPasswordLayout({ children }: { children: React.ReactNode }) { - return children; +export default function ResetPasswordLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Reset Password - Plane" }]; diff --git a/apps/web/app/(all)/accounts/set-password/layout.tsx b/apps/web/app/(all)/accounts/set-password/layout.tsx index 89bf9748d..290d153a9 100644 --- a/apps/web/app/(all)/accounts/set-password/layout.tsx +++ b/apps/web/app/(all)/accounts/set-password/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Set Password - Plane", -}; - -export default function SetPasswordLayout({ children }: { children: React.ReactNode }) { - return children; +export default function SetPasswordLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Set Password - Plane" }]; diff --git a/apps/web/app/(all)/create-workspace/layout.tsx b/apps/web/app/(all)/create-workspace/layout.tsx index 991c9c759..880b1f394 100644 --- a/apps/web/app/(all)/create-workspace/layout.tsx +++ b/apps/web/app/(all)/create-workspace/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Create Workspace", -}; - -export default function CreateWorkspaceLayout({ children }: { children: React.ReactNode }) { - return children; +export default function CreateWorkspaceLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Create Workspace" }]; diff --git a/apps/web/app/(all)/create-workspace/page.tsx b/apps/web/app/(all)/create-workspace/page.tsx index db497729f..06708b44b 100644 --- a/apps/web/app/(all)/create-workspace/page.tsx +++ b/apps/web/app/(all)/create-workspace/page.tsx @@ -9,6 +9,8 @@ import { useTranslation } from "@plane/i18n"; import { Button, getButtonStyling } from "@plane/propel/button"; import { PlaneLogo } from "@plane/propel/icons"; import type { IWorkspace } from "@plane/types"; +// assets +import WorkspaceCreationDisabled from "@/app/assets/workspace/workspace-creation-disabled.png?url"; // components import { CreateWorkspaceForm } from "@/components/workspace/create-workspace-form"; // hooks @@ -18,10 +20,8 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; // plane web helpers import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper"; -// images -import WorkspaceCreationDisabled from "@/public/workspace/workspace-creation-disabled.png"; -const CreateWorkspacePage = observer(() => { +function CreateWorkspacePage() { const { t } = useTranslation(); // router const router = useAppRouter(); @@ -103,6 +103,6 @@ const CreateWorkspacePage = observer(() => {
); -}); +} -export default CreateWorkspacePage; +export default observer(CreateWorkspacePage); diff --git a/apps/web/app/(all)/installations/[provider]/layout.tsx b/apps/web/app/(all)/installations/[provider]/layout.tsx index 51978de9e..862fc5472 100644 --- a/apps/web/app/(all)/installations/[provider]/layout.tsx +++ b/apps/web/app/(all)/installations/[provider]/layout.tsx @@ -1,3 +1,8 @@ -export default function InstallationProviderLayout({ children }: { children: React.ReactNode }) { - return children; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; + +export default function InstallationProviderLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Installations" }]; diff --git a/apps/web/app/(all)/installations/[provider]/page.tsx b/apps/web/app/(all)/installations/[provider]/page.tsx index 03d224f58..ebb995a5d 100644 --- a/apps/web/app/(all)/installations/[provider]/page.tsx +++ b/apps/web/app/(all)/installations/[provider]/page.tsx @@ -1,18 +1,19 @@ "use client"; -import React, { useEffect } from "react"; -import { useParams, useSearchParams } from "next/navigation"; +import { useEffect } from "react"; +import { useSearchParams } from "next/navigation"; // ui import { LogoSpinner } from "@/components/common/logo-spinner"; // services import { AppInstallationService } from "@/services/app_installation.service"; +import type { Route } from "./+types/page"; // services const appInstallationService = new AppInstallationService(); -export default function AppPostInstallation() { +export default function AppPostInstallation({ params }: Route.ComponentProps) { // params - const { provider } = useParams(); + const { provider } = params; // query params const searchParams = useSearchParams(); const installation_id = searchParams.get("installation_id"); diff --git a/apps/web/app/(all)/invitations/layout.tsx b/apps/web/app/(all)/invitations/layout.tsx index 0f05c3444..cbda5f4c0 100644 --- a/apps/web/app/(all)/invitations/layout.tsx +++ b/apps/web/app/(all)/invitations/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Invitations", -}; - -export default function InvitationsLayout({ children }: { children: React.ReactNode }) { - return children; +export default function InvitationsLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Invitations" }]; diff --git a/apps/web/app/(all)/invitations/page.tsx b/apps/web/app/(all)/invitations/page.tsx index 080322ba8..b67bc9b61 100644 --- a/apps/web/app/(all)/invitations/page.tsx +++ b/apps/web/app/(all)/invitations/page.tsx @@ -15,6 +15,8 @@ import { PlaneLogo } from "@plane/propel/icons"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { IWorkspaceMemberInvitation } from "@plane/types"; import { truncateText } from "@plane/utils"; +// assets +import emptyInvitation from "@/app/assets/empty-state/invitation.svg?url"; // components import { EmptyState } from "@/components/common/empty-state"; import { WorkspaceLogo } from "@/components/workspace/logo"; @@ -29,12 +31,10 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; // plane web services import { WorkspaceService } from "@/plane-web/services"; -// images -import emptyInvitation from "@/public/empty-state/invitation.svg"; const workspaceService = new WorkspaceService(); -const UserInvitationsPage = observer(() => { +function UserInvitationsPage() { // states const [invitationsRespond, setInvitationsRespond] = useState([]); const [isJoiningWorkspaces, setIsJoiningWorkspaces] = useState(false); @@ -220,6 +220,6 @@ const UserInvitationsPage = observer(() => { ); -}); +} -export default UserInvitationsPage; +export default observer(UserInvitationsPage); diff --git a/apps/web/app/(all)/layout.preload.tsx b/apps/web/app/(all)/layout.preload.tsx index fb72b72a5..e44c0e400 100644 --- a/apps/web/app/(all)/layout.preload.tsx +++ b/apps/web/app/(all)/layout.preload.tsx @@ -1,28 +1,25 @@ "use client"; -import { useEffect } from "react"; -import ReactDOM from "react-dom"; - +// TODO: Check if we need this // https://nextjs.org/docs/app/api-reference/functions/generate-metadata#link-relpreload -export const usePreloadResources = () => { - useEffect(() => { - const preloadItem = (url: string) => { - ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" }); - }; +// export const usePreloadResources = () => { +// useEffect(() => { +// const preloadItem = (url: string) => { +// ReactDOM.preload(url, { as: "fetch", crossOrigin: "use-credentials" }); +// }; - const urls = [ - `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/instances/`, - `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/`, - `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/profile/`, - `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/settings/`, - `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/workspaces/?v=${Date.now()}`, - ]; +// const urls = [ +// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/instances/`, +// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/`, +// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/profile/`, +// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/settings/`, +// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/users/me/workspaces/?v=${Date.now()}`, +// ]; - urls.forEach((url) => preloadItem(url)); - }, []); -}; +// urls.forEach((url) => preloadItem(url)); +// }, []); +// }; -export const PreloadResources = () => { - usePreloadResources(); - return null; -}; +export const PreloadResources = () => + // usePreloadResources(); + null; diff --git a/apps/web/app/(all)/layout.tsx b/apps/web/app/(all)/layout.tsx index 72bf0c206..e87557cb7 100644 --- a/apps/web/app/(all)/layout.tsx +++ b/apps/web/app/(all)/layout.tsx @@ -1,31 +1,23 @@ -import type { Metadata, Viewport } from "next"; - +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; import { PreloadResources } from "./layout.preload"; +// types // styles import "@/styles/power-k.css"; import "@/styles/emoji.css"; import "@plane/propel/styles/react-day-picker.css"; -export const metadata: Metadata = { - robots: { - index: false, - follow: false, - }, -}; +export const meta: Route.MetaFunction = () => [ + { name: "robots", content: "noindex, nofollow" }, + { name: "viewport", content: "width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover" }, +]; -export const viewport: Viewport = { - minimumScale: 1, - initialScale: 1, - width: "device-width", - viewportFit: "cover", -}; - -export default function AppLayout({ children }: { children: React.ReactNode }) { +export default function AppLayout() { return ( <> - {children} + ); } diff --git a/apps/web/app/(all)/onboarding/layout.tsx b/apps/web/app/(all)/onboarding/layout.tsx index cad1f92cf..ac20091f2 100644 --- a/apps/web/app/(all)/onboarding/layout.tsx +++ b/apps/web/app/(all)/onboarding/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Onboarding", -}; - -export default function OnboardingLayout({ children }: { children: React.ReactNode }) { - return children; +export default function OnboardingLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Onboarding" }]; diff --git a/apps/web/app/(all)/onboarding/page.tsx b/apps/web/app/(all)/onboarding/page.tsx index 14ef5881f..bbcdc5585 100644 --- a/apps/web/app/(all)/onboarding/page.tsx +++ b/apps/web/app/(all)/onboarding/page.tsx @@ -20,7 +20,7 @@ import { WorkspaceService } from "@/plane-web/services"; const workspaceService = new WorkspaceService(); -const OnboardingPage = observer(() => { +function OnboardingPage() { // store hooks const { data: user } = useUser(); const { fetchWorkspaces } = useWorkspace(); @@ -57,6 +57,6 @@ const OnboardingPage = observer(() => { ); -}); +} -export default OnboardingPage; +export default observer(OnboardingPage); diff --git a/apps/web/app/(all)/profile/activity/page.tsx b/apps/web/app/(all)/profile/activity/page.tsx index d978b9a04..39c4c479c 100644 --- a/apps/web/app/(all)/profile/activity/page.tsx +++ b/apps/web/app/(all)/profile/activity/page.tsx @@ -2,30 +2,34 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; // plane imports import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/propel/button"; +// assets +import darkActivityAsset from "@/app/assets/empty-state/profile/activity-dark.webp?url"; +import lightActivityAsset from "@/app/assets/empty-state/profile/activity-light.webp?url"; // components import { PageHead } from "@/components/core/page-title"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; import { ProfileActivityListPage } from "@/components/profile/activity/profile-activity-list"; import { ProfileSettingContentHeader } from "@/components/profile/profile-setting-content-header"; import { ProfileSettingContentWrapper } from "@/components/profile/profile-setting-content-wrapper"; -// hooks -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const PER_PAGE = 100; -const ProfileActivityPage = observer(() => { +function ProfileActivityPage() { // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); const [resultsCount, setResultsCount] = useState(0); const [isEmpty, setIsEmpty] = useState(false); + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // derived values - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" }); + const resolvedPath = resolvedTheme === "light" ? lightActivityAsset : darkActivityAsset; const updateTotalPages = (count: number) => setTotalPages(count); @@ -76,6 +80,6 @@ const ProfileActivityPage = observer(() => { ); -}); +} -export default ProfileActivityPage; +export default observer(ProfileActivityPage); diff --git a/apps/web/app/(all)/profile/appearance/page.tsx b/apps/web/app/(all)/profile/appearance/page.tsx index bbcbe2439..e9c1ee96e 100644 --- a/apps/web/app/(all)/profile/appearance/page.tsx +++ b/apps/web/app/(all)/profile/appearance/page.tsx @@ -20,7 +20,7 @@ import { ProfileSettingContentWrapper } from "@/components/profile/profile-setti // hooks import { useUserProfile } from "@/hooks/store/user"; -const ProfileAppearancePage = observer(() => { +function ProfileAppearancePage() { const { t } = useTranslation(); const { setTheme } = useTheme(); // states @@ -86,6 +86,6 @@ const ProfileAppearancePage = observer(() => { )} ); -}); +} -export default ProfileAppearancePage; +export default observer(ProfileAppearancePage); diff --git a/apps/web/app/(all)/profile/layout.tsx b/apps/web/app/(all)/profile/layout.tsx index aee7779a0..e86f60681 100644 --- a/apps/web/app/(all)/profile/layout.tsx +++ b/apps/web/app/(all)/profile/layout.tsx @@ -1,19 +1,14 @@ "use client"; -import type { ReactNode } from "react"; +// components +import { Outlet } from "react-router"; // wrappers import { ProjectsAppPowerKProvider } from "@/components/power-k/projects-app-provider"; import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper"; // layout import { ProfileLayoutSidebar } from "./sidebar"; -type Props = { - children: ReactNode; -}; - -export default function ProfileSettingsLayout(props: Props) { - const { children } = props; - +export default function ProfileSettingsLayout() { return ( <> @@ -21,7 +16,9 @@ export default function ProfileSettingsLayout(props: Props) {
-
{children}
+
+ +
diff --git a/apps/web/app/(all)/profile/page.tsx b/apps/web/app/(all)/profile/page.tsx index 01ff11145..b89759fd4 100644 --- a/apps/web/app/(all)/profile/page.tsx +++ b/apps/web/app/(all)/profile/page.tsx @@ -11,7 +11,7 @@ import { ProfileSettingContentWrapper } from "@/components/profile/profile-setti // hooks import { useUser } from "@/hooks/store/user"; -const ProfileSettingsPage = observer(() => { +function ProfileSettingsPage() { const { t } = useTranslation(); // store hooks const { data: currentUser, userProfile } = useUser(); @@ -31,6 +31,6 @@ const ProfileSettingsPage = observer(() => { ); -}); +} -export default ProfileSettingsPage; +export default observer(ProfileSettingsPage); diff --git a/apps/web/app/(all)/profile/security/page.tsx b/apps/web/app/(all)/profile/security/page.tsx index 4f0ad13c7..a0ee1f472 100644 --- a/apps/web/app/(all)/profile/security/page.tsx +++ b/apps/web/app/(all)/profile/security/page.tsx @@ -43,7 +43,7 @@ const defaultShowPassword = { confirmPassword: false, }; -const SecurityPage = observer(() => { +const SecurityPage = () => { // store const { data: currentUser, changePassword } = useUser(); // states @@ -254,6 +254,6 @@ const SecurityPage = observer(() => { ); -}); +}; -export default SecurityPage; +export default observer(SecurityPage); diff --git a/apps/web/app/(all)/sign-up/layout.tsx b/apps/web/app/(all)/sign-up/layout.tsx index 815fe08fc..daf5a0307 100644 --- a/apps/web/app/(all)/sign-up/layout.tsx +++ b/apps/web/app/(all)/sign-up/layout.tsx @@ -1,13 +1,11 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Sign up - Plane", - robots: { - index: true, - follow: false, - }, -}; +export const meta: Route.MetaFunction = () => [ + { title: "Sign up - Plane" }, + { name: "robots", content: "index, nofollow" }, +]; -export default function SignUpLayout({ children }: { children: React.ReactNode }) { - return children; +export default function SignUpLayout() { + return ; } diff --git a/apps/web/app/(all)/workspace-invitations/layout.tsx b/apps/web/app/(all)/workspace-invitations/layout.tsx index 535b2f62f..cab6733a6 100644 --- a/apps/web/app/(all)/workspace-invitations/layout.tsx +++ b/apps/web/app/(all)/workspace-invitations/layout.tsx @@ -1,9 +1,8 @@ -import type { Metadata } from "next"; +import { Outlet } from "react-router"; +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - title: "Workspace Invitations", -}; - -export default function WorkspaceInvitationsLayout({ children }: { children: React.ReactNode }) { - return children; +export default function WorkspaceInvitationsLayout() { + return ; } + +export const meta: Route.MetaFunction = () => [{ title: "Workspace Invitations" }]; diff --git a/apps/web/app/(all)/workspace-invitations/page.tsx b/apps/web/app/(all)/workspace-invitations/page.tsx index 7883b08c6..67d9b6c5a 100644 --- a/apps/web/app/(all)/workspace-invitations/page.tsx +++ b/apps/web/app/(all)/workspace-invitations/page.tsx @@ -24,7 +24,7 @@ import { WorkspaceService } from "@/plane-web/services"; // service initialization const workspaceService = new WorkspaceService(); -const WorkspaceInvitationPage = observer(() => { +function WorkspaceInvitationPage() { // router const router = useAppRouter(); // query params @@ -124,6 +124,6 @@ const WorkspaceInvitationPage = observer(() => { ); -}); +} -export default WorkspaceInvitationPage; +export default observer(WorkspaceInvitationPage); diff --git a/apps/web/app/(home)/layout.tsx b/apps/web/app/(home)/layout.tsx index d50131fc0..a972f662a 100644 --- a/apps/web/app/(home)/layout.tsx +++ b/apps/web/app/(home)/layout.tsx @@ -1,19 +1,12 @@ -import type { Metadata, Viewport } from "next"; +import { Outlet } from "react-router"; +// types +import type { Route } from "./+types/layout"; -export const metadata: Metadata = { - robots: { - index: true, - follow: false, - }, -}; +export const meta: Route.MetaFunction = () => [ + { name: "robots", content: "index, nofollow" }, + { name: "viewport", content: "width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover" }, +]; -export const viewport: Viewport = { - minimumScale: 1, - initialScale: 1, - width: "device-width", - viewportFit: "cover", -}; - -export default function HomeLayout({ children }: { children: React.ReactNode }) { - return <>{children}; +export default function HomeLayout() { + return ; } diff --git a/apps/web/public/404.svg b/apps/web/app/assets/404.svg similarity index 100% rename from apps/web/public/404.svg rename to apps/web/app/assets/404.svg diff --git a/apps/web/public/attachment/audio-icon.png b/apps/web/app/assets/attachment/audio-icon.png similarity index 100% rename from apps/web/public/attachment/audio-icon.png rename to apps/web/app/assets/attachment/audio-icon.png diff --git a/apps/web/public/attachment/css-icon.png b/apps/web/app/assets/attachment/css-icon.png similarity index 100% rename from apps/web/public/attachment/css-icon.png rename to apps/web/app/assets/attachment/css-icon.png diff --git a/apps/web/public/attachment/csv-icon.png b/apps/web/app/assets/attachment/csv-icon.png similarity index 100% rename from apps/web/public/attachment/csv-icon.png rename to apps/web/app/assets/attachment/csv-icon.png diff --git a/apps/web/public/attachment/default-icon.png b/apps/web/app/assets/attachment/default-icon.png similarity index 100% rename from apps/web/public/attachment/default-icon.png rename to apps/web/app/assets/attachment/default-icon.png diff --git a/apps/web/public/attachment/doc-icon.png b/apps/web/app/assets/attachment/doc-icon.png similarity index 100% rename from apps/web/public/attachment/doc-icon.png rename to apps/web/app/assets/attachment/doc-icon.png diff --git a/apps/web/public/attachment/excel-icon.png b/apps/web/app/assets/attachment/excel-icon.png similarity index 100% rename from apps/web/public/attachment/excel-icon.png rename to apps/web/app/assets/attachment/excel-icon.png diff --git a/apps/web/public/attachment/figma-icon.png b/apps/web/app/assets/attachment/figma-icon.png similarity index 100% rename from apps/web/public/attachment/figma-icon.png rename to apps/web/app/assets/attachment/figma-icon.png diff --git a/apps/web/public/attachment/html-icon.png b/apps/web/app/assets/attachment/html-icon.png similarity index 100% rename from apps/web/public/attachment/html-icon.png rename to apps/web/app/assets/attachment/html-icon.png diff --git a/apps/web/public/attachment/img-icon.png b/apps/web/app/assets/attachment/img-icon.png similarity index 100% rename from apps/web/public/attachment/img-icon.png rename to apps/web/app/assets/attachment/img-icon.png diff --git a/apps/web/public/attachment/jpg-icon.png b/apps/web/app/assets/attachment/jpg-icon.png similarity index 100% rename from apps/web/public/attachment/jpg-icon.png rename to apps/web/app/assets/attachment/jpg-icon.png diff --git a/apps/web/public/attachment/js-icon.png b/apps/web/app/assets/attachment/js-icon.png similarity index 100% rename from apps/web/public/attachment/js-icon.png rename to apps/web/app/assets/attachment/js-icon.png diff --git a/apps/web/public/attachment/pdf-icon.png b/apps/web/app/assets/attachment/pdf-icon.png similarity index 100% rename from apps/web/public/attachment/pdf-icon.png rename to apps/web/app/assets/attachment/pdf-icon.png diff --git a/apps/web/public/attachment/png-icon.png b/apps/web/app/assets/attachment/png-icon.png similarity index 100% rename from apps/web/public/attachment/png-icon.png rename to apps/web/app/assets/attachment/png-icon.png diff --git a/apps/web/public/attachment/rar-icon.png b/apps/web/app/assets/attachment/rar-icon.png similarity index 100% rename from apps/web/public/attachment/rar-icon.png rename to apps/web/app/assets/attachment/rar-icon.png diff --git a/apps/web/public/attachment/svg-icon.png b/apps/web/app/assets/attachment/svg-icon.png similarity index 100% rename from apps/web/public/attachment/svg-icon.png rename to apps/web/app/assets/attachment/svg-icon.png diff --git a/apps/web/public/attachment/txt-icon.png b/apps/web/app/assets/attachment/txt-icon.png similarity index 100% rename from apps/web/public/attachment/txt-icon.png rename to apps/web/app/assets/attachment/txt-icon.png diff --git a/apps/web/public/attachment/video-icon.png b/apps/web/app/assets/attachment/video-icon.png similarity index 100% rename from apps/web/public/attachment/video-icon.png rename to apps/web/app/assets/attachment/video-icon.png diff --git a/apps/web/public/attachment/zip-icon.png b/apps/web/app/assets/attachment/zip-icon.png similarity index 100% rename from apps/web/public/attachment/zip-icon.png rename to apps/web/app/assets/attachment/zip-icon.png diff --git a/apps/web/public/auth/access-denied.svg b/apps/web/app/assets/auth/access-denied.svg similarity index 100% rename from apps/web/public/auth/access-denied.svg rename to apps/web/app/assets/auth/access-denied.svg diff --git a/apps/web/public/auth/background-pattern-dark.svg b/apps/web/app/assets/auth/background-pattern-dark.svg similarity index 100% rename from apps/web/public/auth/background-pattern-dark.svg rename to apps/web/app/assets/auth/background-pattern-dark.svg diff --git a/apps/web/public/auth/background-pattern.svg b/apps/web/app/assets/auth/background-pattern.svg similarity index 100% rename from apps/web/public/auth/background-pattern.svg rename to apps/web/app/assets/auth/background-pattern.svg diff --git a/apps/web/public/auth/project-not-authorized.svg b/apps/web/app/assets/auth/project-not-authorized.svg similarity index 100% rename from apps/web/public/auth/project-not-authorized.svg rename to apps/web/app/assets/auth/project-not-authorized.svg diff --git a/apps/web/public/auth/unauthorized.svg b/apps/web/app/assets/auth/unauthorized.svg similarity index 100% rename from apps/web/public/auth/unauthorized.svg rename to apps/web/app/assets/auth/unauthorized.svg diff --git a/apps/web/public/auth/workspace-not-authorized.svg b/apps/web/app/assets/auth/workspace-not-authorized.svg similarity index 100% rename from apps/web/public/auth/workspace-not-authorized.svg rename to apps/web/app/assets/auth/workspace-not-authorized.svg diff --git a/apps/web/public/emoji/project-emoji.svg b/apps/web/app/assets/emoji/project-emoji.svg similarity index 100% rename from apps/web/public/emoji/project-emoji.svg rename to apps/web/app/assets/emoji/project-emoji.svg diff --git a/apps/web/public/empty-state/active-cycle/assignee-dark.webp b/apps/web/app/assets/empty-state/active-cycle/assignee-dark.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/assignee-dark.webp rename to apps/web/app/assets/empty-state/active-cycle/assignee-dark.webp diff --git a/apps/web/public/empty-state/active-cycle/assignee-light.webp b/apps/web/app/assets/empty-state/active-cycle/assignee-light.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/assignee-light.webp rename to apps/web/app/assets/empty-state/active-cycle/assignee-light.webp diff --git a/apps/web/public/empty-state/active-cycle/chart-dark.webp b/apps/web/app/assets/empty-state/active-cycle/chart-dark.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/chart-dark.webp rename to apps/web/app/assets/empty-state/active-cycle/chart-dark.webp diff --git a/apps/web/public/empty-state/active-cycle/chart-light.webp b/apps/web/app/assets/empty-state/active-cycle/chart-light.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/chart-light.webp rename to apps/web/app/assets/empty-state/active-cycle/chart-light.webp diff --git a/apps/web/public/empty-state/active-cycle/cycle-dark.webp b/apps/web/app/assets/empty-state/active-cycle/cycle-dark.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/cycle-dark.webp rename to apps/web/app/assets/empty-state/active-cycle/cycle-dark.webp diff --git a/apps/web/public/empty-state/active-cycle/cycle-light.webp b/apps/web/app/assets/empty-state/active-cycle/cycle-light.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/cycle-light.webp rename to apps/web/app/assets/empty-state/active-cycle/cycle-light.webp diff --git a/apps/web/public/empty-state/active-cycle/label-dark.webp b/apps/web/app/assets/empty-state/active-cycle/label-dark.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/label-dark.webp rename to apps/web/app/assets/empty-state/active-cycle/label-dark.webp diff --git a/apps/web/public/empty-state/active-cycle/label-light.webp b/apps/web/app/assets/empty-state/active-cycle/label-light.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/label-light.webp rename to apps/web/app/assets/empty-state/active-cycle/label-light.webp diff --git a/apps/web/public/empty-state/active-cycle/priority-dark.webp b/apps/web/app/assets/empty-state/active-cycle/priority-dark.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/priority-dark.webp rename to apps/web/app/assets/empty-state/active-cycle/priority-dark.webp diff --git a/apps/web/public/empty-state/active-cycle/priority-light.webp b/apps/web/app/assets/empty-state/active-cycle/priority-light.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/priority-light.webp rename to apps/web/app/assets/empty-state/active-cycle/priority-light.webp diff --git a/apps/web/public/empty-state/active-cycle/progress-dark.webp b/apps/web/app/assets/empty-state/active-cycle/progress-dark.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/progress-dark.webp rename to apps/web/app/assets/empty-state/active-cycle/progress-dark.webp diff --git a/apps/web/public/empty-state/active-cycle/progress-light.webp b/apps/web/app/assets/empty-state/active-cycle/progress-light.webp similarity index 100% rename from apps/web/public/empty-state/active-cycle/progress-light.webp rename to apps/web/app/assets/empty-state/active-cycle/progress-light.webp diff --git a/apps/web/public/empty-state/all-issues/all-issues-dark.webp b/apps/web/app/assets/empty-state/all-issues/all-issues-dark.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/all-issues-dark.webp rename to apps/web/app/assets/empty-state/all-issues/all-issues-dark.webp diff --git a/apps/web/public/empty-state/all-issues/all-issues-light.webp b/apps/web/app/assets/empty-state/all-issues/all-issues-light.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/all-issues-light.webp rename to apps/web/app/assets/empty-state/all-issues/all-issues-light.webp diff --git a/apps/web/public/empty-state/all-issues/assigned-dark.webp b/apps/web/app/assets/empty-state/all-issues/assigned-dark.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/assigned-dark.webp rename to apps/web/app/assets/empty-state/all-issues/assigned-dark.webp diff --git a/apps/web/public/empty-state/all-issues/assigned-light.webp b/apps/web/app/assets/empty-state/all-issues/assigned-light.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/assigned-light.webp rename to apps/web/app/assets/empty-state/all-issues/assigned-light.webp diff --git a/apps/web/public/empty-state/all-issues/created-dark.webp b/apps/web/app/assets/empty-state/all-issues/created-dark.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/created-dark.webp rename to apps/web/app/assets/empty-state/all-issues/created-dark.webp diff --git a/apps/web/public/empty-state/all-issues/created-light.webp b/apps/web/app/assets/empty-state/all-issues/created-light.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/created-light.webp rename to apps/web/app/assets/empty-state/all-issues/created-light.webp diff --git a/apps/web/public/empty-state/all-issues/custom-view-dark.webp b/apps/web/app/assets/empty-state/all-issues/custom-view-dark.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/custom-view-dark.webp rename to apps/web/app/assets/empty-state/all-issues/custom-view-dark.webp diff --git a/apps/web/public/empty-state/all-issues/custom-view-light.webp b/apps/web/app/assets/empty-state/all-issues/custom-view-light.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/custom-view-light.webp rename to apps/web/app/assets/empty-state/all-issues/custom-view-light.webp diff --git a/apps/web/public/empty-state/all-issues/no-project-dark.webp b/apps/web/app/assets/empty-state/all-issues/no-project-dark.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/no-project-dark.webp rename to apps/web/app/assets/empty-state/all-issues/no-project-dark.webp diff --git a/apps/web/public/empty-state/all-issues/no-project-light.webp b/apps/web/app/assets/empty-state/all-issues/no-project-light.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/no-project-light.webp rename to apps/web/app/assets/empty-state/all-issues/no-project-light.webp diff --git a/apps/web/public/empty-state/all-issues/subscribed-dark.webp b/apps/web/app/assets/empty-state/all-issues/subscribed-dark.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/subscribed-dark.webp rename to apps/web/app/assets/empty-state/all-issues/subscribed-dark.webp diff --git a/apps/web/public/empty-state/all-issues/subscribed-light.webp b/apps/web/app/assets/empty-state/all-issues/subscribed-light.webp similarity index 100% rename from apps/web/public/empty-state/all-issues/subscribed-light.webp rename to apps/web/app/assets/empty-state/all-issues/subscribed-light.webp diff --git a/apps/web/public/empty-state/analytics/empty-chart-area-dark.webp b/apps/web/app/assets/empty-state/analytics/empty-chart-area-dark.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-chart-area-dark.webp rename to apps/web/app/assets/empty-state/analytics/empty-chart-area-dark.webp diff --git a/apps/web/public/empty-state/analytics/empty-chart-area-light.webp b/apps/web/app/assets/empty-state/analytics/empty-chart-area-light.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-chart-area-light.webp rename to apps/web/app/assets/empty-state/analytics/empty-chart-area-light.webp diff --git a/apps/web/public/empty-state/analytics/empty-chart-bar-dark.webp b/apps/web/app/assets/empty-state/analytics/empty-chart-bar-dark.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-chart-bar-dark.webp rename to apps/web/app/assets/empty-state/analytics/empty-chart-bar-dark.webp diff --git a/apps/web/public/empty-state/analytics/empty-chart-bar-light.webp b/apps/web/app/assets/empty-state/analytics/empty-chart-bar-light.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-chart-bar-light.webp rename to apps/web/app/assets/empty-state/analytics/empty-chart-bar-light.webp diff --git a/apps/web/public/empty-state/analytics/empty-chart-radar-dark.webp b/apps/web/app/assets/empty-state/analytics/empty-chart-radar-dark.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-chart-radar-dark.webp rename to apps/web/app/assets/empty-state/analytics/empty-chart-radar-dark.webp diff --git a/apps/web/public/empty-state/analytics/empty-chart-radar-light.webp b/apps/web/app/assets/empty-state/analytics/empty-chart-radar-light.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-chart-radar-light.webp rename to apps/web/app/assets/empty-state/analytics/empty-chart-radar-light.webp diff --git a/apps/web/public/empty-state/analytics/empty-grid-background-dark.webp b/apps/web/app/assets/empty-state/analytics/empty-grid-background-dark.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-grid-background-dark.webp rename to apps/web/app/assets/empty-state/analytics/empty-grid-background-dark.webp diff --git a/apps/web/public/empty-state/analytics/empty-grid-background-light.webp b/apps/web/app/assets/empty-state/analytics/empty-grid-background-light.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-grid-background-light.webp rename to apps/web/app/assets/empty-state/analytics/empty-grid-background-light.webp diff --git a/apps/web/public/empty-state/analytics/empty-table-dark.webp b/apps/web/app/assets/empty-state/analytics/empty-table-dark.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-table-dark.webp rename to apps/web/app/assets/empty-state/analytics/empty-table-dark.webp diff --git a/apps/web/public/empty-state/analytics/empty-table-light.webp b/apps/web/app/assets/empty-state/analytics/empty-table-light.webp similarity index 100% rename from apps/web/public/empty-state/analytics/empty-table-light.webp rename to apps/web/app/assets/empty-state/analytics/empty-table-light.webp diff --git a/apps/web/public/empty-state/api-token.svg b/apps/web/app/assets/empty-state/api-token.svg similarity index 100% rename from apps/web/public/empty-state/api-token.svg rename to apps/web/app/assets/empty-state/api-token.svg diff --git a/apps/web/public/empty-state/archived/empty-cycles-dark.webp b/apps/web/app/assets/empty-state/archived/empty-cycles-dark.webp similarity index 100% rename from apps/web/public/empty-state/archived/empty-cycles-dark.webp rename to apps/web/app/assets/empty-state/archived/empty-cycles-dark.webp diff --git a/apps/web/public/empty-state/archived/empty-cycles-light.webp b/apps/web/app/assets/empty-state/archived/empty-cycles-light.webp similarity index 100% rename from apps/web/public/empty-state/archived/empty-cycles-light.webp rename to apps/web/app/assets/empty-state/archived/empty-cycles-light.webp diff --git a/apps/web/public/empty-state/archived/empty-issues-dark.webp b/apps/web/app/assets/empty-state/archived/empty-issues-dark.webp similarity index 100% rename from apps/web/public/empty-state/archived/empty-issues-dark.webp rename to apps/web/app/assets/empty-state/archived/empty-issues-dark.webp diff --git a/apps/web/public/empty-state/archived/empty-issues-light.webp b/apps/web/app/assets/empty-state/archived/empty-issues-light.webp similarity index 100% rename from apps/web/public/empty-state/archived/empty-issues-light.webp rename to apps/web/app/assets/empty-state/archived/empty-issues-light.webp diff --git a/apps/web/public/empty-state/archived/empty-modules-dark.webp b/apps/web/app/assets/empty-state/archived/empty-modules-dark.webp similarity index 100% rename from apps/web/public/empty-state/archived/empty-modules-dark.webp rename to apps/web/app/assets/empty-state/archived/empty-modules-dark.webp diff --git a/apps/web/public/empty-state/archived/empty-modules-light.webp b/apps/web/app/assets/empty-state/archived/empty-modules-light.webp similarity index 100% rename from apps/web/public/empty-state/archived/empty-modules-light.webp rename to apps/web/app/assets/empty-state/archived/empty-modules-light.webp diff --git a/apps/web/public/empty-state/cycle-issues/calendar-dark-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/calendar-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/calendar-dark-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/calendar-dark-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/calendar-dark.webp b/apps/web/app/assets/empty-state/cycle-issues/calendar-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/calendar-dark.webp rename to apps/web/app/assets/empty-state/cycle-issues/calendar-dark.webp diff --git a/apps/web/public/empty-state/cycle-issues/calendar-light-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/calendar-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/calendar-light-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/calendar-light-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/calendar-light.webp b/apps/web/app/assets/empty-state/cycle-issues/calendar-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/calendar-light.webp rename to apps/web/app/assets/empty-state/cycle-issues/calendar-light.webp diff --git a/apps/web/public/empty-state/cycle-issues/gantt_chart-dark-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/gantt_chart-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/gantt_chart-dark-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/gantt_chart-dark-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/gantt_chart-dark.webp b/apps/web/app/assets/empty-state/cycle-issues/gantt_chart-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/gantt_chart-dark.webp rename to apps/web/app/assets/empty-state/cycle-issues/gantt_chart-dark.webp diff --git a/apps/web/public/empty-state/cycle-issues/gantt_chart-light-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/gantt_chart-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/gantt_chart-light-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/gantt_chart-light-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/gantt_chart-light.webp b/apps/web/app/assets/empty-state/cycle-issues/gantt_chart-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/gantt_chart-light.webp rename to apps/web/app/assets/empty-state/cycle-issues/gantt_chart-light.webp diff --git a/apps/web/public/empty-state/cycle-issues/kanban-dark-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/kanban-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/kanban-dark-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/kanban-dark-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/kanban-dark.webp b/apps/web/app/assets/empty-state/cycle-issues/kanban-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/kanban-dark.webp rename to apps/web/app/assets/empty-state/cycle-issues/kanban-dark.webp diff --git a/apps/web/public/empty-state/cycle-issues/kanban-light-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/kanban-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/kanban-light-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/kanban-light-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/kanban-light.webp b/apps/web/app/assets/empty-state/cycle-issues/kanban-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/kanban-light.webp rename to apps/web/app/assets/empty-state/cycle-issues/kanban-light.webp diff --git a/apps/web/public/empty-state/cycle-issues/list-dark-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/list-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/list-dark-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/list-dark-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/list-dark.webp b/apps/web/app/assets/empty-state/cycle-issues/list-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/list-dark.webp rename to apps/web/app/assets/empty-state/cycle-issues/list-dark.webp diff --git a/apps/web/public/empty-state/cycle-issues/list-light-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/list-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/list-light-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/list-light-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/list-light.webp b/apps/web/app/assets/empty-state/cycle-issues/list-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/list-light.webp rename to apps/web/app/assets/empty-state/cycle-issues/list-light.webp diff --git a/apps/web/public/empty-state/cycle-issues/spreadsheet-dark-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/spreadsheet-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/spreadsheet-dark-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/spreadsheet-dark-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/spreadsheet-dark.webp b/apps/web/app/assets/empty-state/cycle-issues/spreadsheet-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/spreadsheet-dark.webp rename to apps/web/app/assets/empty-state/cycle-issues/spreadsheet-dark.webp diff --git a/apps/web/public/empty-state/cycle-issues/spreadsheet-light-resp.webp b/apps/web/app/assets/empty-state/cycle-issues/spreadsheet-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/spreadsheet-light-resp.webp rename to apps/web/app/assets/empty-state/cycle-issues/spreadsheet-light-resp.webp diff --git a/apps/web/public/empty-state/cycle-issues/spreadsheet-light.webp b/apps/web/app/assets/empty-state/cycle-issues/spreadsheet-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle-issues/spreadsheet-light.webp rename to apps/web/app/assets/empty-state/cycle-issues/spreadsheet-light.webp diff --git a/apps/web/public/empty-state/cycle.svg b/apps/web/app/assets/empty-state/cycle.svg similarity index 100% rename from apps/web/public/empty-state/cycle.svg rename to apps/web/app/assets/empty-state/cycle.svg diff --git a/apps/web/public/empty-state/cycle/active-dark.webp b/apps/web/app/assets/empty-state/cycle/active-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle/active-dark.webp rename to apps/web/app/assets/empty-state/cycle/active-dark.webp diff --git a/apps/web/public/empty-state/cycle/active-light.webp b/apps/web/app/assets/empty-state/cycle/active-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle/active-light.webp rename to apps/web/app/assets/empty-state/cycle/active-light.webp diff --git a/apps/web/public/empty-state/cycle/all-filters.svg b/apps/web/app/assets/empty-state/cycle/all-filters.svg similarity index 100% rename from apps/web/public/empty-state/cycle/all-filters.svg rename to apps/web/app/assets/empty-state/cycle/all-filters.svg diff --git a/apps/web/public/empty-state/cycle/completed-dark.webp b/apps/web/app/assets/empty-state/cycle/completed-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle/completed-dark.webp rename to apps/web/app/assets/empty-state/cycle/completed-dark.webp diff --git a/apps/web/public/empty-state/cycle/completed-light.webp b/apps/web/app/assets/empty-state/cycle/completed-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle/completed-light.webp rename to apps/web/app/assets/empty-state/cycle/completed-light.webp diff --git a/apps/web/public/empty-state/cycle/completed-no-issues-dark.webp b/apps/web/app/assets/empty-state/cycle/completed-no-issues-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle/completed-no-issues-dark.webp rename to apps/web/app/assets/empty-state/cycle/completed-no-issues-dark.webp diff --git a/apps/web/public/empty-state/cycle/completed-no-issues-light.webp b/apps/web/app/assets/empty-state/cycle/completed-no-issues-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle/completed-no-issues-light.webp rename to apps/web/app/assets/empty-state/cycle/completed-no-issues-light.webp diff --git a/apps/web/public/empty-state/cycle/draft-dark.webp b/apps/web/app/assets/empty-state/cycle/draft-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle/draft-dark.webp rename to apps/web/app/assets/empty-state/cycle/draft-dark.webp diff --git a/apps/web/public/empty-state/cycle/draft-light.webp b/apps/web/app/assets/empty-state/cycle/draft-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle/draft-light.webp rename to apps/web/app/assets/empty-state/cycle/draft-light.webp diff --git a/apps/web/public/empty-state/cycle/name-filter.svg b/apps/web/app/assets/empty-state/cycle/name-filter.svg similarity index 100% rename from apps/web/public/empty-state/cycle/name-filter.svg rename to apps/web/app/assets/empty-state/cycle/name-filter.svg diff --git a/apps/web/public/empty-state/cycle/upcoming-dark.webp b/apps/web/app/assets/empty-state/cycle/upcoming-dark.webp similarity index 100% rename from apps/web/public/empty-state/cycle/upcoming-dark.webp rename to apps/web/app/assets/empty-state/cycle/upcoming-dark.webp diff --git a/apps/web/public/empty-state/cycle/upcoming-light.webp b/apps/web/app/assets/empty-state/cycle/upcoming-light.webp similarity index 100% rename from apps/web/public/empty-state/cycle/upcoming-light.webp rename to apps/web/app/assets/empty-state/cycle/upcoming-light.webp diff --git a/apps/web/public/empty-state/dashboard/dark/completed-issues.svg b/apps/web/app/assets/empty-state/dashboard/dark/completed-issues.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/completed-issues.svg rename to apps/web/app/assets/empty-state/dashboard/dark/completed-issues.svg diff --git a/apps/web/public/empty-state/dashboard/dark/issues-by-priority.svg b/apps/web/app/assets/empty-state/dashboard/dark/issues-by-priority.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/issues-by-priority.svg rename to apps/web/app/assets/empty-state/dashboard/dark/issues-by-priority.svg diff --git a/apps/web/public/empty-state/dashboard/dark/issues-by-state-group.svg b/apps/web/app/assets/empty-state/dashboard/dark/issues-by-state-group.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/issues-by-state-group.svg rename to apps/web/app/assets/empty-state/dashboard/dark/issues-by-state-group.svg diff --git a/apps/web/public/empty-state/dashboard/dark/overdue-issues.svg b/apps/web/app/assets/empty-state/dashboard/dark/overdue-issues.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/overdue-issues.svg rename to apps/web/app/assets/empty-state/dashboard/dark/overdue-issues.svg diff --git a/apps/web/public/empty-state/dashboard/dark/recent-activity.svg b/apps/web/app/assets/empty-state/dashboard/dark/recent-activity.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/recent-activity.svg rename to apps/web/app/assets/empty-state/dashboard/dark/recent-activity.svg diff --git a/apps/web/public/empty-state/dashboard/dark/recent-collaborators-1.svg b/apps/web/app/assets/empty-state/dashboard/dark/recent-collaborators-1.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/recent-collaborators-1.svg rename to apps/web/app/assets/empty-state/dashboard/dark/recent-collaborators-1.svg diff --git a/apps/web/public/empty-state/dashboard/dark/recent-collaborators-2.svg b/apps/web/app/assets/empty-state/dashboard/dark/recent-collaborators-2.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/recent-collaborators-2.svg rename to apps/web/app/assets/empty-state/dashboard/dark/recent-collaborators-2.svg diff --git a/apps/web/public/empty-state/dashboard/dark/recent-collaborators-3.svg b/apps/web/app/assets/empty-state/dashboard/dark/recent-collaborators-3.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/recent-collaborators-3.svg rename to apps/web/app/assets/empty-state/dashboard/dark/recent-collaborators-3.svg diff --git a/apps/web/public/empty-state/dashboard/dark/upcoming-issues.svg b/apps/web/app/assets/empty-state/dashboard/dark/upcoming-issues.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/dark/upcoming-issues.svg rename to apps/web/app/assets/empty-state/dashboard/dark/upcoming-issues.svg diff --git a/apps/web/public/empty-state/dashboard/light/completed-issues.svg b/apps/web/app/assets/empty-state/dashboard/light/completed-issues.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/completed-issues.svg rename to apps/web/app/assets/empty-state/dashboard/light/completed-issues.svg diff --git a/apps/web/public/empty-state/dashboard/light/issues-by-priority.svg b/apps/web/app/assets/empty-state/dashboard/light/issues-by-priority.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/issues-by-priority.svg rename to apps/web/app/assets/empty-state/dashboard/light/issues-by-priority.svg diff --git a/apps/web/public/empty-state/dashboard/light/issues-by-state-group.svg b/apps/web/app/assets/empty-state/dashboard/light/issues-by-state-group.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/issues-by-state-group.svg rename to apps/web/app/assets/empty-state/dashboard/light/issues-by-state-group.svg diff --git a/apps/web/public/empty-state/dashboard/light/overdue-issues.svg b/apps/web/app/assets/empty-state/dashboard/light/overdue-issues.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/overdue-issues.svg rename to apps/web/app/assets/empty-state/dashboard/light/overdue-issues.svg diff --git a/apps/web/public/empty-state/dashboard/light/recent-activity.svg b/apps/web/app/assets/empty-state/dashboard/light/recent-activity.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/recent-activity.svg rename to apps/web/app/assets/empty-state/dashboard/light/recent-activity.svg diff --git a/apps/web/public/empty-state/dashboard/light/recent-collaborators-1.svg b/apps/web/app/assets/empty-state/dashboard/light/recent-collaborators-1.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/recent-collaborators-1.svg rename to apps/web/app/assets/empty-state/dashboard/light/recent-collaborators-1.svg diff --git a/apps/web/public/empty-state/dashboard/light/recent-collaborators-2.svg b/apps/web/app/assets/empty-state/dashboard/light/recent-collaborators-2.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/recent-collaborators-2.svg rename to apps/web/app/assets/empty-state/dashboard/light/recent-collaborators-2.svg diff --git a/apps/web/public/empty-state/dashboard/light/recent-collaborators-3.svg b/apps/web/app/assets/empty-state/dashboard/light/recent-collaborators-3.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/recent-collaborators-3.svg rename to apps/web/app/assets/empty-state/dashboard/light/recent-collaborators-3.svg diff --git a/apps/web/public/empty-state/dashboard/light/upcoming-issues.svg b/apps/web/app/assets/empty-state/dashboard/light/upcoming-issues.svg similarity index 100% rename from apps/web/public/empty-state/dashboard/light/upcoming-issues.svg rename to apps/web/app/assets/empty-state/dashboard/light/upcoming-issues.svg diff --git a/apps/web/public/empty-state/dashboard/widgets-dark.webp b/apps/web/app/assets/empty-state/dashboard/widgets-dark.webp similarity index 100% rename from apps/web/public/empty-state/dashboard/widgets-dark.webp rename to apps/web/app/assets/empty-state/dashboard/widgets-dark.webp diff --git a/apps/web/public/empty-state/dashboard/widgets-light.webp b/apps/web/app/assets/empty-state/dashboard/widgets-light.webp similarity index 100% rename from apps/web/public/empty-state/dashboard/widgets-light.webp rename to apps/web/app/assets/empty-state/dashboard/widgets-light.webp diff --git a/apps/web/public/empty-state/dashboard_empty_project.webp b/apps/web/app/assets/empty-state/dashboard_empty_project.webp similarity index 100% rename from apps/web/public/empty-state/dashboard_empty_project.webp rename to apps/web/app/assets/empty-state/dashboard_empty_project.webp diff --git a/apps/web/public/empty-state/disabled-feature/cycles-dark.webp b/apps/web/app/assets/empty-state/disabled-feature/cycles-dark.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/cycles-dark.webp rename to apps/web/app/assets/empty-state/disabled-feature/cycles-dark.webp diff --git a/apps/web/public/empty-state/disabled-feature/cycles-light.webp b/apps/web/app/assets/empty-state/disabled-feature/cycles-light.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/cycles-light.webp rename to apps/web/app/assets/empty-state/disabled-feature/cycles-light.webp diff --git a/apps/web/public/empty-state/disabled-feature/intake-dark.webp b/apps/web/app/assets/empty-state/disabled-feature/intake-dark.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/intake-dark.webp rename to apps/web/app/assets/empty-state/disabled-feature/intake-dark.webp diff --git a/apps/web/public/empty-state/disabled-feature/intake-light.webp b/apps/web/app/assets/empty-state/disabled-feature/intake-light.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/intake-light.webp rename to apps/web/app/assets/empty-state/disabled-feature/intake-light.webp diff --git a/apps/web/public/empty-state/disabled-feature/modules-dark.webp b/apps/web/app/assets/empty-state/disabled-feature/modules-dark.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/modules-dark.webp rename to apps/web/app/assets/empty-state/disabled-feature/modules-dark.webp diff --git a/apps/web/public/empty-state/disabled-feature/modules-light.webp b/apps/web/app/assets/empty-state/disabled-feature/modules-light.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/modules-light.webp rename to apps/web/app/assets/empty-state/disabled-feature/modules-light.webp diff --git a/apps/web/public/empty-state/disabled-feature/pages-dark.webp b/apps/web/app/assets/empty-state/disabled-feature/pages-dark.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/pages-dark.webp rename to apps/web/app/assets/empty-state/disabled-feature/pages-dark.webp diff --git a/apps/web/public/empty-state/disabled-feature/pages-light.webp b/apps/web/app/assets/empty-state/disabled-feature/pages-light.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/pages-light.webp rename to apps/web/app/assets/empty-state/disabled-feature/pages-light.webp diff --git a/apps/web/public/empty-state/disabled-feature/views-dark.webp b/apps/web/app/assets/empty-state/disabled-feature/views-dark.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/views-dark.webp rename to apps/web/app/assets/empty-state/disabled-feature/views-dark.webp diff --git a/apps/web/public/empty-state/disabled-feature/views-light.webp b/apps/web/app/assets/empty-state/disabled-feature/views-light.webp similarity index 100% rename from apps/web/public/empty-state/disabled-feature/views-light.webp rename to apps/web/app/assets/empty-state/disabled-feature/views-light.webp diff --git a/apps/web/public/empty-state/draft/draft-issues-empty-dark.webp b/apps/web/app/assets/empty-state/draft/draft-issues-empty-dark.webp similarity index 100% rename from apps/web/public/empty-state/draft/draft-issues-empty-dark.webp rename to apps/web/app/assets/empty-state/draft/draft-issues-empty-dark.webp diff --git a/apps/web/public/empty-state/draft/draft-issues-empty-light.webp b/apps/web/app/assets/empty-state/draft/draft-issues-empty-light.webp similarity index 100% rename from apps/web/public/empty-state/draft/draft-issues-empty-light.webp rename to apps/web/app/assets/empty-state/draft/draft-issues-empty-light.webp diff --git a/apps/web/public/empty-state/empty-filters/calendar-dark.webp b/apps/web/app/assets/empty-state/empty-filters/calendar-dark.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/calendar-dark.webp rename to apps/web/app/assets/empty-state/empty-filters/calendar-dark.webp diff --git a/apps/web/public/empty-state/empty-filters/calendar-light.webp b/apps/web/app/assets/empty-state/empty-filters/calendar-light.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/calendar-light.webp rename to apps/web/app/assets/empty-state/empty-filters/calendar-light.webp diff --git a/apps/web/public/empty-state/empty-filters/gantt_chart-dark.webp b/apps/web/app/assets/empty-state/empty-filters/gantt_chart-dark.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/gantt_chart-dark.webp rename to apps/web/app/assets/empty-state/empty-filters/gantt_chart-dark.webp diff --git a/apps/web/public/empty-state/empty-filters/gantt_chart-light.webp b/apps/web/app/assets/empty-state/empty-filters/gantt_chart-light.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/gantt_chart-light.webp rename to apps/web/app/assets/empty-state/empty-filters/gantt_chart-light.webp diff --git a/apps/web/public/empty-state/empty-filters/kanban-dark.webp b/apps/web/app/assets/empty-state/empty-filters/kanban-dark.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/kanban-dark.webp rename to apps/web/app/assets/empty-state/empty-filters/kanban-dark.webp diff --git a/apps/web/public/empty-state/empty-filters/kanban-light.webp b/apps/web/app/assets/empty-state/empty-filters/kanban-light.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/kanban-light.webp rename to apps/web/app/assets/empty-state/empty-filters/kanban-light.webp diff --git a/apps/web/public/empty-state/empty-filters/list-dark.webp b/apps/web/app/assets/empty-state/empty-filters/list-dark.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/list-dark.webp rename to apps/web/app/assets/empty-state/empty-filters/list-dark.webp diff --git a/apps/web/public/empty-state/empty-filters/list-light.webp b/apps/web/app/assets/empty-state/empty-filters/list-light.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/list-light.webp rename to apps/web/app/assets/empty-state/empty-filters/list-light.webp diff --git a/apps/web/public/empty-state/empty-filters/spreadsheet-dark.webp b/apps/web/app/assets/empty-state/empty-filters/spreadsheet-dark.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/spreadsheet-dark.webp rename to apps/web/app/assets/empty-state/empty-filters/spreadsheet-dark.webp diff --git a/apps/web/public/empty-state/empty-filters/spreadsheet-light.webp b/apps/web/app/assets/empty-state/empty-filters/spreadsheet-light.webp similarity index 100% rename from apps/web/public/empty-state/empty-filters/spreadsheet-light.webp rename to apps/web/app/assets/empty-state/empty-filters/spreadsheet-light.webp diff --git a/apps/web/public/empty-state/empty-updates-light.png b/apps/web/app/assets/empty-state/empty-updates-light.png similarity index 100% rename from apps/web/public/empty-state/empty-updates-light.png rename to apps/web/app/assets/empty-state/empty-updates-light.png diff --git a/apps/web/public/empty-state/empty_analytics.webp b/apps/web/app/assets/empty-state/empty_analytics.webp similarity index 100% rename from apps/web/public/empty-state/empty_analytics.webp rename to apps/web/app/assets/empty-state/empty_analytics.webp diff --git a/apps/web/public/empty-state/empty_bar_graph.svg b/apps/web/app/assets/empty-state/empty_bar_graph.svg similarity index 100% rename from apps/web/public/empty-state/empty_bar_graph.svg rename to apps/web/app/assets/empty-state/empty_bar_graph.svg diff --git a/apps/web/public/empty-state/empty_cycles.webp b/apps/web/app/assets/empty-state/empty_cycles.webp similarity index 100% rename from apps/web/public/empty-state/empty_cycles.webp rename to apps/web/app/assets/empty-state/empty_cycles.webp diff --git a/apps/web/public/empty-state/empty_graph.svg b/apps/web/app/assets/empty-state/empty_graph.svg similarity index 100% rename from apps/web/public/empty-state/empty_graph.svg rename to apps/web/app/assets/empty-state/empty_graph.svg diff --git a/apps/web/public/empty-state/empty_issues.webp b/apps/web/app/assets/empty-state/empty_issues.webp similarity index 100% rename from apps/web/public/empty-state/empty_issues.webp rename to apps/web/app/assets/empty-state/empty_issues.webp diff --git a/apps/web/public/empty-state/empty_label.svg b/apps/web/app/assets/empty-state/empty_label.svg similarity index 100% rename from apps/web/public/empty-state/empty_label.svg rename to apps/web/app/assets/empty-state/empty_label.svg diff --git a/apps/web/public/empty-state/empty_members.svg b/apps/web/app/assets/empty-state/empty_members.svg similarity index 100% rename from apps/web/public/empty-state/empty_members.svg rename to apps/web/app/assets/empty-state/empty_members.svg diff --git a/apps/web/public/empty-state/empty_modules.webp b/apps/web/app/assets/empty-state/empty_modules.webp similarity index 100% rename from apps/web/public/empty-state/empty_modules.webp rename to apps/web/app/assets/empty-state/empty_modules.webp diff --git a/apps/web/public/empty-state/empty_page.png b/apps/web/app/assets/empty-state/empty_page.png similarity index 100% rename from apps/web/public/empty-state/empty_page.png rename to apps/web/app/assets/empty-state/empty_page.png diff --git a/apps/web/public/empty-state/empty_project.webp b/apps/web/app/assets/empty-state/empty_project.webp similarity index 100% rename from apps/web/public/empty-state/empty_project.webp rename to apps/web/app/assets/empty-state/empty_project.webp diff --git a/apps/web/public/empty-state/empty_users.svg b/apps/web/app/assets/empty-state/empty_users.svg similarity index 100% rename from apps/web/public/empty-state/empty_users.svg rename to apps/web/app/assets/empty-state/empty_users.svg diff --git a/apps/web/public/empty-state/empty_view.webp b/apps/web/app/assets/empty-state/empty_view.webp similarity index 100% rename from apps/web/public/empty-state/empty_view.webp rename to apps/web/app/assets/empty-state/empty_view.webp diff --git a/apps/web/public/empty-state/epics/epics-dark.webp b/apps/web/app/assets/empty-state/epics/epics-dark.webp similarity index 100% rename from apps/web/public/empty-state/epics/epics-dark.webp rename to apps/web/app/assets/empty-state/epics/epics-dark.webp diff --git a/apps/web/public/empty-state/epics/epics-light.webp b/apps/web/app/assets/empty-state/epics/epics-light.webp similarity index 100% rename from apps/web/public/empty-state/epics/epics-light.webp rename to apps/web/app/assets/empty-state/epics/epics-light.webp diff --git a/apps/web/public/empty-state/epics/settings-dark.webp b/apps/web/app/assets/empty-state/epics/settings-dark.webp similarity index 100% rename from apps/web/public/empty-state/epics/settings-dark.webp rename to apps/web/app/assets/empty-state/epics/settings-dark.webp diff --git a/apps/web/public/empty-state/epics/settings-light.webp b/apps/web/app/assets/empty-state/epics/settings-light.webp similarity index 100% rename from apps/web/public/empty-state/epics/settings-light.webp rename to apps/web/app/assets/empty-state/epics/settings-light.webp diff --git a/apps/web/public/empty-state/estimates/dark.svg b/apps/web/app/assets/empty-state/estimates/dark.svg similarity index 100% rename from apps/web/public/empty-state/estimates/dark.svg rename to apps/web/app/assets/empty-state/estimates/dark.svg diff --git a/apps/web/public/empty-state/estimates/light.svg b/apps/web/app/assets/empty-state/estimates/light.svg similarity index 100% rename from apps/web/public/empty-state/estimates/light.svg rename to apps/web/app/assets/empty-state/estimates/light.svg diff --git a/apps/web/public/empty-state/intake/filter-issue-dark.webp b/apps/web/app/assets/empty-state/intake/filter-issue-dark.webp similarity index 100% rename from apps/web/public/empty-state/intake/filter-issue-dark.webp rename to apps/web/app/assets/empty-state/intake/filter-issue-dark.webp diff --git a/apps/web/public/empty-state/intake/filter-issue-light.webp b/apps/web/app/assets/empty-state/intake/filter-issue-light.webp similarity index 100% rename from apps/web/public/empty-state/intake/filter-issue-light.webp rename to apps/web/app/assets/empty-state/intake/filter-issue-light.webp diff --git a/apps/web/public/empty-state/intake/intake-dark-resp.webp b/apps/web/app/assets/empty-state/intake/intake-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/intake/intake-dark-resp.webp rename to apps/web/app/assets/empty-state/intake/intake-dark-resp.webp diff --git a/apps/web/public/empty-state/intake/intake-dark.webp b/apps/web/app/assets/empty-state/intake/intake-dark.webp similarity index 100% rename from apps/web/public/empty-state/intake/intake-dark.webp rename to apps/web/app/assets/empty-state/intake/intake-dark.webp diff --git a/apps/web/public/empty-state/intake/intake-issue-dark.webp b/apps/web/app/assets/empty-state/intake/intake-issue-dark.webp similarity index 100% rename from apps/web/public/empty-state/intake/intake-issue-dark.webp rename to apps/web/app/assets/empty-state/intake/intake-issue-dark.webp diff --git a/apps/web/public/empty-state/intake/intake-issue-light.webp b/apps/web/app/assets/empty-state/intake/intake-issue-light.webp similarity index 100% rename from apps/web/public/empty-state/intake/intake-issue-light.webp rename to apps/web/app/assets/empty-state/intake/intake-issue-light.webp diff --git a/apps/web/public/empty-state/intake/intake-light-resp.webp b/apps/web/app/assets/empty-state/intake/intake-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/intake/intake-light-resp.webp rename to apps/web/app/assets/empty-state/intake/intake-light-resp.webp diff --git a/apps/web/public/empty-state/intake/intake-light.webp b/apps/web/app/assets/empty-state/intake/intake-light.webp similarity index 100% rename from apps/web/public/empty-state/intake/intake-light.webp rename to apps/web/app/assets/empty-state/intake/intake-light.webp diff --git a/apps/web/public/empty-state/intake/issue-detail-dark.webp b/apps/web/app/assets/empty-state/intake/issue-detail-dark.webp similarity index 100% rename from apps/web/public/empty-state/intake/issue-detail-dark.webp rename to apps/web/app/assets/empty-state/intake/issue-detail-dark.webp diff --git a/apps/web/public/empty-state/intake/issue-detail-light.webp b/apps/web/app/assets/empty-state/intake/issue-detail-light.webp similarity index 100% rename from apps/web/public/empty-state/intake/issue-detail-light.webp rename to apps/web/app/assets/empty-state/intake/issue-detail-light.webp diff --git a/apps/web/public/empty-state/invitation.svg b/apps/web/app/assets/empty-state/invitation.svg similarity index 100% rename from apps/web/public/empty-state/invitation.svg rename to apps/web/app/assets/empty-state/invitation.svg diff --git a/apps/web/public/empty-state/issue.svg b/apps/web/app/assets/empty-state/issue.svg similarity index 100% rename from apps/web/public/empty-state/issue.svg rename to apps/web/app/assets/empty-state/issue.svg diff --git a/apps/web/public/empty-state/label.svg b/apps/web/app/assets/empty-state/label.svg similarity index 100% rename from apps/web/public/empty-state/label.svg rename to apps/web/app/assets/empty-state/label.svg diff --git a/apps/web/public/empty-state/module-issues/calendar-dark-resp.webp b/apps/web/app/assets/empty-state/module-issues/calendar-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/calendar-dark-resp.webp rename to apps/web/app/assets/empty-state/module-issues/calendar-dark-resp.webp diff --git a/apps/web/public/empty-state/module-issues/calendar-dark.webp b/apps/web/app/assets/empty-state/module-issues/calendar-dark.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/calendar-dark.webp rename to apps/web/app/assets/empty-state/module-issues/calendar-dark.webp diff --git a/apps/web/public/empty-state/module-issues/calendar-light-resp.webp b/apps/web/app/assets/empty-state/module-issues/calendar-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/calendar-light-resp.webp rename to apps/web/app/assets/empty-state/module-issues/calendar-light-resp.webp diff --git a/apps/web/public/empty-state/module-issues/calendar-light.webp b/apps/web/app/assets/empty-state/module-issues/calendar-light.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/calendar-light.webp rename to apps/web/app/assets/empty-state/module-issues/calendar-light.webp diff --git a/apps/web/public/empty-state/module-issues/gantt_chart-dark-resp.webp b/apps/web/app/assets/empty-state/module-issues/gantt_chart-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/gantt_chart-dark-resp.webp rename to apps/web/app/assets/empty-state/module-issues/gantt_chart-dark-resp.webp diff --git a/apps/web/public/empty-state/module-issues/gantt_chart-dark.webp b/apps/web/app/assets/empty-state/module-issues/gantt_chart-dark.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/gantt_chart-dark.webp rename to apps/web/app/assets/empty-state/module-issues/gantt_chart-dark.webp diff --git a/apps/web/public/empty-state/module-issues/gantt_chart-light-resp.webp b/apps/web/app/assets/empty-state/module-issues/gantt_chart-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/gantt_chart-light-resp.webp rename to apps/web/app/assets/empty-state/module-issues/gantt_chart-light-resp.webp diff --git a/apps/web/public/empty-state/module-issues/gantt_chart-light.webp b/apps/web/app/assets/empty-state/module-issues/gantt_chart-light.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/gantt_chart-light.webp rename to apps/web/app/assets/empty-state/module-issues/gantt_chart-light.webp diff --git a/apps/web/public/empty-state/module-issues/kanban-dark-resp.webp b/apps/web/app/assets/empty-state/module-issues/kanban-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/kanban-dark-resp.webp rename to apps/web/app/assets/empty-state/module-issues/kanban-dark-resp.webp diff --git a/apps/web/public/empty-state/module-issues/kanban-dark.webp b/apps/web/app/assets/empty-state/module-issues/kanban-dark.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/kanban-dark.webp rename to apps/web/app/assets/empty-state/module-issues/kanban-dark.webp diff --git a/apps/web/public/empty-state/module-issues/kanban-light-resp.webp b/apps/web/app/assets/empty-state/module-issues/kanban-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/kanban-light-resp.webp rename to apps/web/app/assets/empty-state/module-issues/kanban-light-resp.webp diff --git a/apps/web/public/empty-state/module-issues/kanban-light.webp b/apps/web/app/assets/empty-state/module-issues/kanban-light.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/kanban-light.webp rename to apps/web/app/assets/empty-state/module-issues/kanban-light.webp diff --git a/apps/web/public/empty-state/module-issues/list-dark-resp.webp b/apps/web/app/assets/empty-state/module-issues/list-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/list-dark-resp.webp rename to apps/web/app/assets/empty-state/module-issues/list-dark-resp.webp diff --git a/apps/web/public/empty-state/module-issues/list-dark.webp b/apps/web/app/assets/empty-state/module-issues/list-dark.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/list-dark.webp rename to apps/web/app/assets/empty-state/module-issues/list-dark.webp diff --git a/apps/web/public/empty-state/module-issues/list-light-resp.webp b/apps/web/app/assets/empty-state/module-issues/list-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/list-light-resp.webp rename to apps/web/app/assets/empty-state/module-issues/list-light-resp.webp diff --git a/apps/web/public/empty-state/module-issues/list-light.webp b/apps/web/app/assets/empty-state/module-issues/list-light.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/list-light.webp rename to apps/web/app/assets/empty-state/module-issues/list-light.webp diff --git a/apps/web/public/empty-state/module-issues/spreadsheet-dark-resp.webp b/apps/web/app/assets/empty-state/module-issues/spreadsheet-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/spreadsheet-dark-resp.webp rename to apps/web/app/assets/empty-state/module-issues/spreadsheet-dark-resp.webp diff --git a/apps/web/public/empty-state/module-issues/spreadsheet-dark.webp b/apps/web/app/assets/empty-state/module-issues/spreadsheet-dark.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/spreadsheet-dark.webp rename to apps/web/app/assets/empty-state/module-issues/spreadsheet-dark.webp diff --git a/apps/web/public/empty-state/module-issues/spreadsheet-light-resp.webp b/apps/web/app/assets/empty-state/module-issues/spreadsheet-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/spreadsheet-light-resp.webp rename to apps/web/app/assets/empty-state/module-issues/spreadsheet-light-resp.webp diff --git a/apps/web/public/empty-state/module-issues/spreadsheet-light.webp b/apps/web/app/assets/empty-state/module-issues/spreadsheet-light.webp similarity index 100% rename from apps/web/public/empty-state/module-issues/spreadsheet-light.webp rename to apps/web/app/assets/empty-state/module-issues/spreadsheet-light.webp diff --git a/apps/web/public/empty-state/module.svg b/apps/web/app/assets/empty-state/module.svg similarity index 100% rename from apps/web/public/empty-state/module.svg rename to apps/web/app/assets/empty-state/module.svg diff --git a/apps/web/public/empty-state/module/all-filters.svg b/apps/web/app/assets/empty-state/module/all-filters.svg similarity index 100% rename from apps/web/public/empty-state/module/all-filters.svg rename to apps/web/app/assets/empty-state/module/all-filters.svg diff --git a/apps/web/public/empty-state/module/name-filter.svg b/apps/web/app/assets/empty-state/module/name-filter.svg similarity index 100% rename from apps/web/public/empty-state/module/name-filter.svg rename to apps/web/app/assets/empty-state/module/name-filter.svg diff --git a/apps/web/public/empty-state/notification.svg b/apps/web/app/assets/empty-state/notification.svg similarity index 100% rename from apps/web/public/empty-state/notification.svg rename to apps/web/app/assets/empty-state/notification.svg diff --git a/apps/web/public/empty-state/onboarding/analytics-dark.webp b/apps/web/app/assets/empty-state/onboarding/analytics-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/analytics-dark.webp rename to apps/web/app/assets/empty-state/onboarding/analytics-dark.webp diff --git a/apps/web/public/empty-state/onboarding/analytics-light.webp b/apps/web/app/assets/empty-state/onboarding/analytics-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/analytics-light.webp rename to apps/web/app/assets/empty-state/onboarding/analytics-light.webp diff --git a/apps/web/public/empty-state/onboarding/archive-dark.png b/apps/web/app/assets/empty-state/onboarding/archive-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/archive-dark.png rename to apps/web/app/assets/empty-state/onboarding/archive-dark.png diff --git a/apps/web/public/empty-state/onboarding/archive-light.png b/apps/web/app/assets/empty-state/onboarding/archive-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/archive-light.png rename to apps/web/app/assets/empty-state/onboarding/archive-light.png diff --git a/apps/web/public/empty-state/onboarding/cycles-dark.webp b/apps/web/app/assets/empty-state/onboarding/cycles-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/cycles-dark.webp rename to apps/web/app/assets/empty-state/onboarding/cycles-dark.webp diff --git a/apps/web/public/empty-state/onboarding/cycles-light.webp b/apps/web/app/assets/empty-state/onboarding/cycles-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/cycles-light.webp rename to apps/web/app/assets/empty-state/onboarding/cycles-light.webp diff --git a/apps/web/public/empty-state/onboarding/dashboard-dark.webp b/apps/web/app/assets/empty-state/onboarding/dashboard-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/dashboard-dark.webp rename to apps/web/app/assets/empty-state/onboarding/dashboard-dark.webp diff --git a/apps/web/public/empty-state/onboarding/dashboard-light.webp b/apps/web/app/assets/empty-state/onboarding/dashboard-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/dashboard-light.webp rename to apps/web/app/assets/empty-state/onboarding/dashboard-light.webp diff --git a/apps/web/public/empty-state/onboarding/graph-dark.png b/apps/web/app/assets/empty-state/onboarding/graph-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/graph-dark.png rename to apps/web/app/assets/empty-state/onboarding/graph-dark.png diff --git a/apps/web/public/empty-state/onboarding/graph-light.png b/apps/web/app/assets/empty-state/onboarding/graph-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/graph-light.png rename to apps/web/app/assets/empty-state/onboarding/graph-light.png diff --git a/apps/web/public/empty-state/onboarding/issues-closed-dark.png b/apps/web/app/assets/empty-state/onboarding/issues-closed-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/issues-closed-dark.png rename to apps/web/app/assets/empty-state/onboarding/issues-closed-dark.png diff --git a/apps/web/public/empty-state/onboarding/issues-closed-light.png b/apps/web/app/assets/empty-state/onboarding/issues-closed-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/issues-closed-light.png rename to apps/web/app/assets/empty-state/onboarding/issues-closed-light.png diff --git a/apps/web/public/empty-state/onboarding/issues-dark.webp b/apps/web/app/assets/empty-state/onboarding/issues-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/issues-dark.webp rename to apps/web/app/assets/empty-state/onboarding/issues-dark.webp diff --git a/apps/web/public/empty-state/onboarding/issues-light.webp b/apps/web/app/assets/empty-state/onboarding/issues-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/issues-light.webp rename to apps/web/app/assets/empty-state/onboarding/issues-light.webp diff --git a/apps/web/public/empty-state/onboarding/members-dark.png b/apps/web/app/assets/empty-state/onboarding/members-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/members-dark.png rename to apps/web/app/assets/empty-state/onboarding/members-dark.png diff --git a/apps/web/public/empty-state/onboarding/members-light.png b/apps/web/app/assets/empty-state/onboarding/members-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/members-light.png rename to apps/web/app/assets/empty-state/onboarding/members-light.png diff --git a/apps/web/public/empty-state/onboarding/modules-dark.webp b/apps/web/app/assets/empty-state/onboarding/modules-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/modules-dark.webp rename to apps/web/app/assets/empty-state/onboarding/modules-dark.webp diff --git a/apps/web/public/empty-state/onboarding/modules-light.webp b/apps/web/app/assets/empty-state/onboarding/modules-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/modules-light.webp rename to apps/web/app/assets/empty-state/onboarding/modules-light.webp diff --git a/apps/web/public/empty-state/onboarding/notification-dark.png b/apps/web/app/assets/empty-state/onboarding/notification-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/notification-dark.png rename to apps/web/app/assets/empty-state/onboarding/notification-dark.png diff --git a/apps/web/public/empty-state/onboarding/notification-light.png b/apps/web/app/assets/empty-state/onboarding/notification-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/notification-light.png rename to apps/web/app/assets/empty-state/onboarding/notification-light.png diff --git a/apps/web/public/empty-state/onboarding/pages-dark.webp b/apps/web/app/assets/empty-state/onboarding/pages-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/pages-dark.webp rename to apps/web/app/assets/empty-state/onboarding/pages-dark.webp diff --git a/apps/web/public/empty-state/onboarding/pages-light.webp b/apps/web/app/assets/empty-state/onboarding/pages-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/pages-light.webp rename to apps/web/app/assets/empty-state/onboarding/pages-light.webp diff --git a/apps/web/public/empty-state/onboarding/projects-dark.webp b/apps/web/app/assets/empty-state/onboarding/projects-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/projects-dark.webp rename to apps/web/app/assets/empty-state/onboarding/projects-dark.webp diff --git a/apps/web/public/empty-state/onboarding/projects-light.webp b/apps/web/app/assets/empty-state/onboarding/projects-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/projects-light.webp rename to apps/web/app/assets/empty-state/onboarding/projects-light.webp diff --git a/apps/web/public/empty-state/onboarding/search-dark.png b/apps/web/app/assets/empty-state/onboarding/search-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/search-dark.png rename to apps/web/app/assets/empty-state/onboarding/search-dark.png diff --git a/apps/web/public/empty-state/onboarding/search-light.png b/apps/web/app/assets/empty-state/onboarding/search-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/search-light.png rename to apps/web/app/assets/empty-state/onboarding/search-light.png diff --git a/apps/web/public/empty-state/onboarding/snooze-light.png b/apps/web/app/assets/empty-state/onboarding/snooze-light.png similarity index 100% rename from apps/web/public/empty-state/onboarding/snooze-light.png rename to apps/web/app/assets/empty-state/onboarding/snooze-light.png diff --git a/apps/web/public/empty-state/onboarding/snoozed-dark.png b/apps/web/app/assets/empty-state/onboarding/snoozed-dark.png similarity index 100% rename from apps/web/public/empty-state/onboarding/snoozed-dark.png rename to apps/web/app/assets/empty-state/onboarding/snoozed-dark.png diff --git a/apps/web/public/empty-state/onboarding/views-dark.webp b/apps/web/app/assets/empty-state/onboarding/views-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/views-dark.webp rename to apps/web/app/assets/empty-state/onboarding/views-dark.webp diff --git a/apps/web/public/empty-state/onboarding/views-light.webp b/apps/web/app/assets/empty-state/onboarding/views-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/views-light.webp rename to apps/web/app/assets/empty-state/onboarding/views-light.webp diff --git a/apps/web/public/empty-state/onboarding/workspace-active-cycles-dark.webp b/apps/web/app/assets/empty-state/onboarding/workspace-active-cycles-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/workspace-active-cycles-dark.webp rename to apps/web/app/assets/empty-state/onboarding/workspace-active-cycles-dark.webp diff --git a/apps/web/public/empty-state/onboarding/workspace-active-cycles-light.webp b/apps/web/app/assets/empty-state/onboarding/workspace-active-cycles-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/workspace-active-cycles-light.webp rename to apps/web/app/assets/empty-state/onboarding/workspace-active-cycles-light.webp diff --git a/apps/web/public/empty-state/onboarding/workspace-invites-dark.webp b/apps/web/app/assets/empty-state/onboarding/workspace-invites-dark.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/workspace-invites-dark.webp rename to apps/web/app/assets/empty-state/onboarding/workspace-invites-dark.webp diff --git a/apps/web/public/empty-state/onboarding/workspace-invites-light.webp b/apps/web/app/assets/empty-state/onboarding/workspace-invites-light.webp similarity index 100% rename from apps/web/public/empty-state/onboarding/workspace-invites-light.webp rename to apps/web/app/assets/empty-state/onboarding/workspace-invites-light.webp diff --git a/apps/web/public/empty-state/profile/activities-dark.webp b/apps/web/app/assets/empty-state/profile/activities-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/activities-dark.webp rename to apps/web/app/assets/empty-state/profile/activities-dark.webp diff --git a/apps/web/public/empty-state/profile/activities-light.webp b/apps/web/app/assets/empty-state/profile/activities-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/activities-light.webp rename to apps/web/app/assets/empty-state/profile/activities-light.webp diff --git a/apps/web/public/empty-state/profile/activity-dark.webp b/apps/web/app/assets/empty-state/profile/activity-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/activity-dark.webp rename to apps/web/app/assets/empty-state/profile/activity-dark.webp diff --git a/apps/web/public/empty-state/profile/activity-light.webp b/apps/web/app/assets/empty-state/profile/activity-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/activity-light.webp rename to apps/web/app/assets/empty-state/profile/activity-light.webp diff --git a/apps/web/public/empty-state/profile/assigned-dark.webp b/apps/web/app/assets/empty-state/profile/assigned-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/assigned-dark.webp rename to apps/web/app/assets/empty-state/profile/assigned-dark.webp diff --git a/apps/web/public/empty-state/profile/assigned-light.webp b/apps/web/app/assets/empty-state/profile/assigned-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/assigned-light.webp rename to apps/web/app/assets/empty-state/profile/assigned-light.webp diff --git a/apps/web/public/empty-state/profile/created-dark.webp b/apps/web/app/assets/empty-state/profile/created-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/created-dark.webp rename to apps/web/app/assets/empty-state/profile/created-dark.webp diff --git a/apps/web/public/empty-state/profile/created-light.webp b/apps/web/app/assets/empty-state/profile/created-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/created-light.webp rename to apps/web/app/assets/empty-state/profile/created-light.webp diff --git a/apps/web/public/empty-state/profile/issues-by-priority-dark.webp b/apps/web/app/assets/empty-state/profile/issues-by-priority-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/issues-by-priority-dark.webp rename to apps/web/app/assets/empty-state/profile/issues-by-priority-dark.webp diff --git a/apps/web/public/empty-state/profile/issues-by-priority-light.webp b/apps/web/app/assets/empty-state/profile/issues-by-priority-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/issues-by-priority-light.webp rename to apps/web/app/assets/empty-state/profile/issues-by-priority-light.webp diff --git a/apps/web/public/empty-state/profile/issues-by-state-dark.webp b/apps/web/app/assets/empty-state/profile/issues-by-state-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/issues-by-state-dark.webp rename to apps/web/app/assets/empty-state/profile/issues-by-state-dark.webp diff --git a/apps/web/public/empty-state/profile/issues-by-state-light.webp b/apps/web/app/assets/empty-state/profile/issues-by-state-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/issues-by-state-light.webp rename to apps/web/app/assets/empty-state/profile/issues-by-state-light.webp diff --git a/apps/web/public/empty-state/profile/subscribed-dark.webp b/apps/web/app/assets/empty-state/profile/subscribed-dark.webp similarity index 100% rename from apps/web/public/empty-state/profile/subscribed-dark.webp rename to apps/web/app/assets/empty-state/profile/subscribed-dark.webp diff --git a/apps/web/public/empty-state/profile/subscribed-light.webp b/apps/web/app/assets/empty-state/profile/subscribed-light.webp similarity index 100% rename from apps/web/public/empty-state/profile/subscribed-light.webp rename to apps/web/app/assets/empty-state/profile/subscribed-light.webp diff --git a/apps/web/public/empty-state/project-settings/estimates-dark-resp.webp b/apps/web/app/assets/empty-state/project-settings/estimates-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/estimates-dark-resp.webp rename to apps/web/app/assets/empty-state/project-settings/estimates-dark-resp.webp diff --git a/apps/web/public/empty-state/project-settings/estimates-dark.png b/apps/web/app/assets/empty-state/project-settings/estimates-dark.png similarity index 100% rename from apps/web/public/empty-state/project-settings/estimates-dark.png rename to apps/web/app/assets/empty-state/project-settings/estimates-dark.png diff --git a/apps/web/public/empty-state/project-settings/estimates-dark.webp b/apps/web/app/assets/empty-state/project-settings/estimates-dark.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/estimates-dark.webp rename to apps/web/app/assets/empty-state/project-settings/estimates-dark.webp diff --git a/apps/web/public/empty-state/project-settings/estimates-light-resp.webp b/apps/web/app/assets/empty-state/project-settings/estimates-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/estimates-light-resp.webp rename to apps/web/app/assets/empty-state/project-settings/estimates-light-resp.webp diff --git a/apps/web/public/empty-state/project-settings/estimates-light.png b/apps/web/app/assets/empty-state/project-settings/estimates-light.png similarity index 100% rename from apps/web/public/empty-state/project-settings/estimates-light.png rename to apps/web/app/assets/empty-state/project-settings/estimates-light.png diff --git a/apps/web/public/empty-state/project-settings/estimates-light.webp b/apps/web/app/assets/empty-state/project-settings/estimates-light.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/estimates-light.webp rename to apps/web/app/assets/empty-state/project-settings/estimates-light.webp diff --git a/apps/web/public/empty-state/project-settings/integrations-dark-resp.webp b/apps/web/app/assets/empty-state/project-settings/integrations-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/integrations-dark-resp.webp rename to apps/web/app/assets/empty-state/project-settings/integrations-dark-resp.webp diff --git a/apps/web/public/empty-state/project-settings/integrations-dark.webp b/apps/web/app/assets/empty-state/project-settings/integrations-dark.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/integrations-dark.webp rename to apps/web/app/assets/empty-state/project-settings/integrations-dark.webp diff --git a/apps/web/public/empty-state/project-settings/integrations-light-resp.webp b/apps/web/app/assets/empty-state/project-settings/integrations-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/integrations-light-resp.webp rename to apps/web/app/assets/empty-state/project-settings/integrations-light-resp.webp diff --git a/apps/web/public/empty-state/project-settings/integrations-light.webp b/apps/web/app/assets/empty-state/project-settings/integrations-light.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/integrations-light.webp rename to apps/web/app/assets/empty-state/project-settings/integrations-light.webp diff --git a/apps/web/public/empty-state/project-settings/labels-dark-resp.webp b/apps/web/app/assets/empty-state/project-settings/labels-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/labels-dark-resp.webp rename to apps/web/app/assets/empty-state/project-settings/labels-dark-resp.webp diff --git a/apps/web/public/empty-state/project-settings/labels-dark.webp b/apps/web/app/assets/empty-state/project-settings/labels-dark.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/labels-dark.webp rename to apps/web/app/assets/empty-state/project-settings/labels-dark.webp diff --git a/apps/web/public/empty-state/project-settings/labels-light-resp.webp b/apps/web/app/assets/empty-state/project-settings/labels-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/labels-light-resp.webp rename to apps/web/app/assets/empty-state/project-settings/labels-light-resp.webp diff --git a/apps/web/public/empty-state/project-settings/labels-light.webp b/apps/web/app/assets/empty-state/project-settings/labels-light.webp similarity index 100% rename from apps/web/public/empty-state/project-settings/labels-light.webp rename to apps/web/app/assets/empty-state/project-settings/labels-light.webp diff --git a/apps/web/public/empty-state/project-settings/no-projects-dark.png b/apps/web/app/assets/empty-state/project-settings/no-projects-dark.png similarity index 100% rename from apps/web/public/empty-state/project-settings/no-projects-dark.png rename to apps/web/app/assets/empty-state/project-settings/no-projects-dark.png diff --git a/apps/web/public/empty-state/project-settings/no-projects-light.png b/apps/web/app/assets/empty-state/project-settings/no-projects-light.png similarity index 100% rename from apps/web/public/empty-state/project-settings/no-projects-light.png rename to apps/web/app/assets/empty-state/project-settings/no-projects-light.png diff --git a/apps/web/public/empty-state/project-settings/updates-dark.png b/apps/web/app/assets/empty-state/project-settings/updates-dark.png similarity index 100% rename from apps/web/public/empty-state/project-settings/updates-dark.png rename to apps/web/app/assets/empty-state/project-settings/updates-dark.png diff --git a/apps/web/public/empty-state/project-settings/updates-light.png b/apps/web/app/assets/empty-state/project-settings/updates-light.png similarity index 100% rename from apps/web/public/empty-state/project-settings/updates-light.png rename to apps/web/app/assets/empty-state/project-settings/updates-light.png diff --git a/apps/web/public/empty-state/project.svg b/apps/web/app/assets/empty-state/project.svg similarity index 100% rename from apps/web/public/empty-state/project.svg rename to apps/web/app/assets/empty-state/project.svg diff --git a/apps/web/public/empty-state/project/all-filters-dark.svg b/apps/web/app/assets/empty-state/project/all-filters-dark.svg similarity index 100% rename from apps/web/public/empty-state/project/all-filters-dark.svg rename to apps/web/app/assets/empty-state/project/all-filters-dark.svg diff --git a/apps/web/public/empty-state/project/all-filters-light.svg b/apps/web/app/assets/empty-state/project/all-filters-light.svg similarity index 100% rename from apps/web/public/empty-state/project/all-filters-light.svg rename to apps/web/app/assets/empty-state/project/all-filters-light.svg diff --git a/apps/web/public/empty-state/project/name-filter-dark.svg b/apps/web/app/assets/empty-state/project/name-filter-dark.svg similarity index 100% rename from apps/web/public/empty-state/project/name-filter-dark.svg rename to apps/web/app/assets/empty-state/project/name-filter-dark.svg diff --git a/apps/web/public/empty-state/project/name-filter-light.svg b/apps/web/app/assets/empty-state/project/name-filter-light.svg similarity index 100% rename from apps/web/public/empty-state/project/name-filter-light.svg rename to apps/web/app/assets/empty-state/project/name-filter-light.svg diff --git a/apps/web/public/empty-state/project/name-filter.svg b/apps/web/app/assets/empty-state/project/name-filter.svg similarity index 100% rename from apps/web/public/empty-state/project/name-filter.svg rename to apps/web/app/assets/empty-state/project/name-filter.svg diff --git a/apps/web/public/empty-state/recent_activity.svg b/apps/web/app/assets/empty-state/recent_activity.svg similarity index 100% rename from apps/web/public/empty-state/recent_activity.svg rename to apps/web/app/assets/empty-state/recent_activity.svg diff --git a/apps/web/public/empty-state/search/all-issue-view-dark.webp b/apps/web/app/assets/empty-state/search/all-issue-view-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/all-issue-view-dark.webp rename to apps/web/app/assets/empty-state/search/all-issue-view-dark.webp diff --git a/apps/web/public/empty-state/search/all-issues-view-light.webp b/apps/web/app/assets/empty-state/search/all-issues-view-light.webp similarity index 100% rename from apps/web/public/empty-state/search/all-issues-view-light.webp rename to apps/web/app/assets/empty-state/search/all-issues-view-light.webp diff --git a/apps/web/public/empty-state/search/archive-dark.webp b/apps/web/app/assets/empty-state/search/archive-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/archive-dark.webp rename to apps/web/app/assets/empty-state/search/archive-dark.webp diff --git a/apps/web/public/empty-state/search/archive-light.webp b/apps/web/app/assets/empty-state/search/archive-light.webp similarity index 100% rename from apps/web/public/empty-state/search/archive-light.webp rename to apps/web/app/assets/empty-state/search/archive-light.webp diff --git a/apps/web/public/empty-state/search/comments-dark.webp b/apps/web/app/assets/empty-state/search/comments-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/comments-dark.webp rename to apps/web/app/assets/empty-state/search/comments-dark.webp diff --git a/apps/web/public/empty-state/search/comments-light.webp b/apps/web/app/assets/empty-state/search/comments-light.webp similarity index 100% rename from apps/web/public/empty-state/search/comments-light.webp rename to apps/web/app/assets/empty-state/search/comments-light.webp diff --git a/apps/web/public/empty-state/search/issues-dark.webp b/apps/web/app/assets/empty-state/search/issues-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/issues-dark.webp rename to apps/web/app/assets/empty-state/search/issues-dark.webp diff --git a/apps/web/public/empty-state/search/issues-light.webp b/apps/web/app/assets/empty-state/search/issues-light.webp similarity index 100% rename from apps/web/public/empty-state/search/issues-light.webp rename to apps/web/app/assets/empty-state/search/issues-light.webp diff --git a/apps/web/public/empty-state/search/member-dark.webp b/apps/web/app/assets/empty-state/search/member-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/member-dark.webp rename to apps/web/app/assets/empty-state/search/member-dark.webp diff --git a/apps/web/public/empty-state/search/member-light.webp b/apps/web/app/assets/empty-state/search/member-light.webp similarity index 100% rename from apps/web/public/empty-state/search/member-light.webp rename to apps/web/app/assets/empty-state/search/member-light.webp diff --git a/apps/web/public/empty-state/search/notification-dark.webp b/apps/web/app/assets/empty-state/search/notification-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/notification-dark.webp rename to apps/web/app/assets/empty-state/search/notification-dark.webp diff --git a/apps/web/public/empty-state/search/notification-light.webp b/apps/web/app/assets/empty-state/search/notification-light.webp similarity index 100% rename from apps/web/public/empty-state/search/notification-light.webp rename to apps/web/app/assets/empty-state/search/notification-light.webp diff --git a/apps/web/public/empty-state/search/project-dark.webp b/apps/web/app/assets/empty-state/search/project-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/project-dark.webp rename to apps/web/app/assets/empty-state/search/project-dark.webp diff --git a/apps/web/public/empty-state/search/project-light.webp b/apps/web/app/assets/empty-state/search/project-light.webp similarity index 100% rename from apps/web/public/empty-state/search/project-light.webp rename to apps/web/app/assets/empty-state/search/project-light.webp diff --git a/apps/web/public/empty-state/search/search-dark.webp b/apps/web/app/assets/empty-state/search/search-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/search-dark.webp rename to apps/web/app/assets/empty-state/search/search-dark.webp diff --git a/apps/web/public/empty-state/search/search-light.webp b/apps/web/app/assets/empty-state/search/search-light.webp similarity index 100% rename from apps/web/public/empty-state/search/search-light.webp rename to apps/web/app/assets/empty-state/search/search-light.webp diff --git a/apps/web/public/empty-state/search/snooze-dark.webp b/apps/web/app/assets/empty-state/search/snooze-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/snooze-dark.webp rename to apps/web/app/assets/empty-state/search/snooze-dark.webp diff --git a/apps/web/public/empty-state/search/snooze-light.webp b/apps/web/app/assets/empty-state/search/snooze-light.webp similarity index 100% rename from apps/web/public/empty-state/search/snooze-light.webp rename to apps/web/app/assets/empty-state/search/snooze-light.webp diff --git a/apps/web/public/empty-state/search/views-dark.webp b/apps/web/app/assets/empty-state/search/views-dark.webp similarity index 100% rename from apps/web/public/empty-state/search/views-dark.webp rename to apps/web/app/assets/empty-state/search/views-dark.webp diff --git a/apps/web/public/empty-state/search/views-light.webp b/apps/web/app/assets/empty-state/search/views-light.webp similarity index 100% rename from apps/web/public/empty-state/search/views-light.webp rename to apps/web/app/assets/empty-state/search/views-light.webp diff --git a/apps/web/public/empty-state/state_graph.svg b/apps/web/app/assets/empty-state/state_graph.svg similarity index 100% rename from apps/web/public/empty-state/state_graph.svg rename to apps/web/app/assets/empty-state/state_graph.svg diff --git a/apps/web/public/empty-state/stickies/stickies-dark.webp b/apps/web/app/assets/empty-state/stickies/stickies-dark.webp similarity index 100% rename from apps/web/public/empty-state/stickies/stickies-dark.webp rename to apps/web/app/assets/empty-state/stickies/stickies-dark.webp diff --git a/apps/web/public/empty-state/stickies/stickies-light.webp b/apps/web/app/assets/empty-state/stickies/stickies-light.webp similarity index 100% rename from apps/web/public/empty-state/stickies/stickies-light.webp rename to apps/web/app/assets/empty-state/stickies/stickies-light.webp diff --git a/apps/web/public/empty-state/stickies/stickies-search-dark.webp b/apps/web/app/assets/empty-state/stickies/stickies-search-dark.webp similarity index 100% rename from apps/web/public/empty-state/stickies/stickies-search-dark.webp rename to apps/web/app/assets/empty-state/stickies/stickies-search-dark.webp diff --git a/apps/web/public/empty-state/stickies/stickies-search-light.webp b/apps/web/app/assets/empty-state/stickies/stickies-search-light.webp similarity index 100% rename from apps/web/public/empty-state/stickies/stickies-search-light.webp rename to apps/web/app/assets/empty-state/stickies/stickies-search-light.webp diff --git a/apps/web/public/empty-state/view.svg b/apps/web/app/assets/empty-state/view.svg similarity index 100% rename from apps/web/public/empty-state/view.svg rename to apps/web/app/assets/empty-state/view.svg diff --git a/apps/web/public/empty-state/web-hook.svg b/apps/web/app/assets/empty-state/web-hook.svg similarity index 100% rename from apps/web/public/empty-state/web-hook.svg rename to apps/web/app/assets/empty-state/web-hook.svg diff --git a/apps/web/public/empty-state/wiki/all-dark.webp b/apps/web/app/assets/empty-state/wiki/all-dark.webp similarity index 100% rename from apps/web/public/empty-state/wiki/all-dark.webp rename to apps/web/app/assets/empty-state/wiki/all-dark.webp diff --git a/apps/web/public/empty-state/wiki/all-filters-dark.svg b/apps/web/app/assets/empty-state/wiki/all-filters-dark.svg similarity index 100% rename from apps/web/public/empty-state/wiki/all-filters-dark.svg rename to apps/web/app/assets/empty-state/wiki/all-filters-dark.svg diff --git a/apps/web/public/empty-state/wiki/all-filters-light.svg b/apps/web/app/assets/empty-state/wiki/all-filters-light.svg similarity index 100% rename from apps/web/public/empty-state/wiki/all-filters-light.svg rename to apps/web/app/assets/empty-state/wiki/all-filters-light.svg diff --git a/apps/web/public/empty-state/wiki/all-light.webp b/apps/web/app/assets/empty-state/wiki/all-light.webp similarity index 100% rename from apps/web/public/empty-state/wiki/all-light.webp rename to apps/web/app/assets/empty-state/wiki/all-light.webp diff --git a/apps/web/public/empty-state/wiki/archived-dark.webp b/apps/web/app/assets/empty-state/wiki/archived-dark.webp similarity index 100% rename from apps/web/public/empty-state/wiki/archived-dark.webp rename to apps/web/app/assets/empty-state/wiki/archived-dark.webp diff --git a/apps/web/public/empty-state/wiki/archived-light.webp b/apps/web/app/assets/empty-state/wiki/archived-light.webp similarity index 100% rename from apps/web/public/empty-state/wiki/archived-light.webp rename to apps/web/app/assets/empty-state/wiki/archived-light.webp diff --git a/apps/web/public/empty-state/wiki/name-filter-dark.svg b/apps/web/app/assets/empty-state/wiki/name-filter-dark.svg similarity index 100% rename from apps/web/public/empty-state/wiki/name-filter-dark.svg rename to apps/web/app/assets/empty-state/wiki/name-filter-dark.svg diff --git a/apps/web/public/empty-state/wiki/name-filter-light.svg b/apps/web/app/assets/empty-state/wiki/name-filter-light.svg similarity index 100% rename from apps/web/public/empty-state/wiki/name-filter-light.svg rename to apps/web/app/assets/empty-state/wiki/name-filter-light.svg diff --git a/apps/web/public/empty-state/wiki/navigation-pane/assets-dark.webp b/apps/web/app/assets/empty-state/wiki/navigation-pane/assets-dark.webp similarity index 100% rename from apps/web/public/empty-state/wiki/navigation-pane/assets-dark.webp rename to apps/web/app/assets/empty-state/wiki/navigation-pane/assets-dark.webp diff --git a/apps/web/public/empty-state/wiki/navigation-pane/assets-light.webp b/apps/web/app/assets/empty-state/wiki/navigation-pane/assets-light.webp similarity index 100% rename from apps/web/public/empty-state/wiki/navigation-pane/assets-light.webp rename to apps/web/app/assets/empty-state/wiki/navigation-pane/assets-light.webp diff --git a/apps/web/public/empty-state/wiki/navigation-pane/outline-dark.webp b/apps/web/app/assets/empty-state/wiki/navigation-pane/outline-dark.webp similarity index 100% rename from apps/web/public/empty-state/wiki/navigation-pane/outline-dark.webp rename to apps/web/app/assets/empty-state/wiki/navigation-pane/outline-dark.webp diff --git a/apps/web/public/empty-state/wiki/navigation-pane/outline-light.webp b/apps/web/app/assets/empty-state/wiki/navigation-pane/outline-light.webp similarity index 100% rename from apps/web/public/empty-state/wiki/navigation-pane/outline-light.webp rename to apps/web/app/assets/empty-state/wiki/navigation-pane/outline-light.webp diff --git a/apps/web/public/empty-state/wiki/private-dark.webp b/apps/web/app/assets/empty-state/wiki/private-dark.webp similarity index 100% rename from apps/web/public/empty-state/wiki/private-dark.webp rename to apps/web/app/assets/empty-state/wiki/private-dark.webp diff --git a/apps/web/public/empty-state/wiki/private-light.webp b/apps/web/app/assets/empty-state/wiki/private-light.webp similarity index 100% rename from apps/web/public/empty-state/wiki/private-light.webp rename to apps/web/app/assets/empty-state/wiki/private-light.webp diff --git a/apps/web/public/empty-state/wiki/public-dark.webp b/apps/web/app/assets/empty-state/wiki/public-dark.webp similarity index 100% rename from apps/web/public/empty-state/wiki/public-dark.webp rename to apps/web/app/assets/empty-state/wiki/public-dark.webp diff --git a/apps/web/public/empty-state/wiki/public-light.webp b/apps/web/app/assets/empty-state/wiki/public-light.webp similarity index 100% rename from apps/web/public/empty-state/wiki/public-light.webp rename to apps/web/app/assets/empty-state/wiki/public-light.webp diff --git a/apps/web/public/empty-state/workspace-draft/issue-dark.webp b/apps/web/app/assets/empty-state/workspace-draft/issue-dark.webp similarity index 100% rename from apps/web/public/empty-state/workspace-draft/issue-dark.webp rename to apps/web/app/assets/empty-state/workspace-draft/issue-dark.webp diff --git a/apps/web/public/empty-state/workspace-draft/issue-light.webp b/apps/web/app/assets/empty-state/workspace-draft/issue-light.webp similarity index 100% rename from apps/web/public/empty-state/workspace-draft/issue-light.webp rename to apps/web/app/assets/empty-state/workspace-draft/issue-light.webp diff --git a/apps/web/public/empty-state/workspace-settings/api-tokens-dark-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/api-tokens-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/api-tokens-dark-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/api-tokens-dark-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/api-tokens-dark.webp b/apps/web/app/assets/empty-state/workspace-settings/api-tokens-dark.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/api-tokens-dark.webp rename to apps/web/app/assets/empty-state/workspace-settings/api-tokens-dark.webp diff --git a/apps/web/public/empty-state/workspace-settings/api-tokens-light-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/api-tokens-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/api-tokens-light-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/api-tokens-light-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/api-tokens-light.webp b/apps/web/app/assets/empty-state/workspace-settings/api-tokens-light.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/api-tokens-light.webp rename to apps/web/app/assets/empty-state/workspace-settings/api-tokens-light.webp diff --git a/apps/web/public/empty-state/workspace-settings/exports-dark-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/exports-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/exports-dark-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/exports-dark-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/exports-dark.webp b/apps/web/app/assets/empty-state/workspace-settings/exports-dark.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/exports-dark.webp rename to apps/web/app/assets/empty-state/workspace-settings/exports-dark.webp diff --git a/apps/web/public/empty-state/workspace-settings/exports-light-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/exports-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/exports-light-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/exports-light-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/exports-light.webp b/apps/web/app/assets/empty-state/workspace-settings/exports-light.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/exports-light.webp rename to apps/web/app/assets/empty-state/workspace-settings/exports-light.webp diff --git a/apps/web/public/empty-state/workspace-settings/imports-dark-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/imports-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/imports-dark-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/imports-dark-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/imports-dark.webp b/apps/web/app/assets/empty-state/workspace-settings/imports-dark.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/imports-dark.webp rename to apps/web/app/assets/empty-state/workspace-settings/imports-dark.webp diff --git a/apps/web/public/empty-state/workspace-settings/imports-light-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/imports-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/imports-light-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/imports-light-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/imports-light.webp b/apps/web/app/assets/empty-state/workspace-settings/imports-light.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/imports-light.webp rename to apps/web/app/assets/empty-state/workspace-settings/imports-light.webp diff --git a/apps/web/public/empty-state/workspace-settings/integrations-dark-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/integrations-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/integrations-dark-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/integrations-dark-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/integrations-dark.webp b/apps/web/app/assets/empty-state/workspace-settings/integrations-dark.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/integrations-dark.webp rename to apps/web/app/assets/empty-state/workspace-settings/integrations-dark.webp diff --git a/apps/web/public/empty-state/workspace-settings/integrations-light-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/integrations-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/integrations-light-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/integrations-light-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/integrations-light.webp b/apps/web/app/assets/empty-state/workspace-settings/integrations-light.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/integrations-light.webp rename to apps/web/app/assets/empty-state/workspace-settings/integrations-light.webp diff --git a/apps/web/public/empty-state/workspace-settings/webhooks-dark-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/webhooks-dark-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/webhooks-dark-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/webhooks-dark-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/webhooks-dark.webp b/apps/web/app/assets/empty-state/workspace-settings/webhooks-dark.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/webhooks-dark.webp rename to apps/web/app/assets/empty-state/workspace-settings/webhooks-dark.webp diff --git a/apps/web/public/empty-state/workspace-settings/webhooks-light-resp.webp b/apps/web/app/assets/empty-state/workspace-settings/webhooks-light-resp.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/webhooks-light-resp.webp rename to apps/web/app/assets/empty-state/workspace-settings/webhooks-light-resp.webp diff --git a/apps/web/public/empty-state/workspace-settings/webhooks-light.webp b/apps/web/app/assets/empty-state/workspace-settings/webhooks-light.webp similarity index 100% rename from apps/web/public/empty-state/workspace-settings/webhooks-light.webp rename to apps/web/app/assets/empty-state/workspace-settings/webhooks-light.webp diff --git a/apps/web/public/favicon/apple-touch-icon.png b/apps/web/app/assets/favicon/apple-touch-icon.png similarity index 100% rename from apps/web/public/favicon/apple-touch-icon.png rename to apps/web/app/assets/favicon/apple-touch-icon.png diff --git a/apps/web/public/favicon/favicon-16x16.png b/apps/web/app/assets/favicon/favicon-16x16.png similarity index 100% rename from apps/web/public/favicon/favicon-16x16.png rename to apps/web/app/assets/favicon/favicon-16x16.png diff --git a/apps/web/public/favicon/favicon-32x32.png b/apps/web/app/assets/favicon/favicon-32x32.png similarity index 100% rename from apps/web/public/favicon/favicon-32x32.png rename to apps/web/app/assets/favicon/favicon-32x32.png diff --git a/apps/web/public/favicon/favicon.ico b/apps/web/app/assets/favicon/favicon.ico similarity index 100% rename from apps/web/public/favicon/favicon.ico rename to apps/web/app/assets/favicon/favicon.ico diff --git a/apps/web/public/fonts/inter/bold-italic.ttf b/apps/web/app/assets/fonts/inter/bold-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/bold-italic.ttf rename to apps/web/app/assets/fonts/inter/bold-italic.ttf diff --git a/apps/web/public/fonts/inter/bold.ttf b/apps/web/app/assets/fonts/inter/bold.ttf similarity index 100% rename from apps/web/public/fonts/inter/bold.ttf rename to apps/web/app/assets/fonts/inter/bold.ttf diff --git a/apps/web/public/fonts/inter/heavy-italic.ttf b/apps/web/app/assets/fonts/inter/heavy-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/heavy-italic.ttf rename to apps/web/app/assets/fonts/inter/heavy-italic.ttf diff --git a/apps/web/public/fonts/inter/heavy.ttf b/apps/web/app/assets/fonts/inter/heavy.ttf similarity index 100% rename from apps/web/public/fonts/inter/heavy.ttf rename to apps/web/app/assets/fonts/inter/heavy.ttf diff --git a/apps/web/public/fonts/inter/light-italic.ttf b/apps/web/app/assets/fonts/inter/light-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/light-italic.ttf rename to apps/web/app/assets/fonts/inter/light-italic.ttf diff --git a/apps/web/public/fonts/inter/light.ttf b/apps/web/app/assets/fonts/inter/light.ttf similarity index 100% rename from apps/web/public/fonts/inter/light.ttf rename to apps/web/app/assets/fonts/inter/light.ttf diff --git a/apps/web/public/fonts/inter/medium-italic.ttf b/apps/web/app/assets/fonts/inter/medium-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/medium-italic.ttf rename to apps/web/app/assets/fonts/inter/medium-italic.ttf diff --git a/apps/web/public/fonts/inter/medium.ttf b/apps/web/app/assets/fonts/inter/medium.ttf similarity index 100% rename from apps/web/public/fonts/inter/medium.ttf rename to apps/web/app/assets/fonts/inter/medium.ttf diff --git a/apps/web/public/fonts/inter/regular-italic.ttf b/apps/web/app/assets/fonts/inter/regular-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/regular-italic.ttf rename to apps/web/app/assets/fonts/inter/regular-italic.ttf diff --git a/apps/web/public/fonts/inter/regular.ttf b/apps/web/app/assets/fonts/inter/regular.ttf similarity index 100% rename from apps/web/public/fonts/inter/regular.ttf rename to apps/web/app/assets/fonts/inter/regular.ttf diff --git a/apps/web/public/fonts/inter/semibold-italic.ttf b/apps/web/app/assets/fonts/inter/semibold-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/semibold-italic.ttf rename to apps/web/app/assets/fonts/inter/semibold-italic.ttf diff --git a/apps/web/public/fonts/inter/semibold.ttf b/apps/web/app/assets/fonts/inter/semibold.ttf similarity index 100% rename from apps/web/public/fonts/inter/semibold.ttf rename to apps/web/app/assets/fonts/inter/semibold.ttf diff --git a/apps/web/public/fonts/inter/thin-italic.ttf b/apps/web/app/assets/fonts/inter/thin-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/thin-italic.ttf rename to apps/web/app/assets/fonts/inter/thin-italic.ttf diff --git a/apps/web/public/fonts/inter/thin.ttf b/apps/web/app/assets/fonts/inter/thin.ttf similarity index 100% rename from apps/web/public/fonts/inter/thin.ttf rename to apps/web/app/assets/fonts/inter/thin.ttf diff --git a/apps/web/public/fonts/inter/ultrabold-italic.ttf b/apps/web/app/assets/fonts/inter/ultrabold-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/ultrabold-italic.ttf rename to apps/web/app/assets/fonts/inter/ultrabold-italic.ttf diff --git a/apps/web/public/fonts/inter/ultrabold.ttf b/apps/web/app/assets/fonts/inter/ultrabold.ttf similarity index 100% rename from apps/web/public/fonts/inter/ultrabold.ttf rename to apps/web/app/assets/fonts/inter/ultrabold.ttf diff --git a/apps/web/public/fonts/inter/ultralight-italic.ttf b/apps/web/app/assets/fonts/inter/ultralight-italic.ttf similarity index 100% rename from apps/web/public/fonts/inter/ultralight-italic.ttf rename to apps/web/app/assets/fonts/inter/ultralight-italic.ttf diff --git a/apps/web/public/fonts/inter/ultralight.ttf b/apps/web/app/assets/fonts/inter/ultralight.ttf similarity index 100% rename from apps/web/public/fonts/inter/ultralight.ttf rename to apps/web/app/assets/fonts/inter/ultralight.ttf diff --git a/apps/web/public/icons/icon-180x180.png b/apps/web/app/assets/icons/icon-180x180.png similarity index 100% rename from apps/web/public/icons/icon-180x180.png rename to apps/web/app/assets/icons/icon-180x180.png diff --git a/apps/web/app/assets/icons/icon-512x512.png b/apps/web/app/assets/icons/icon-512x512.png new file mode 100644 index 000000000..4c070d079 Binary files /dev/null and b/apps/web/app/assets/icons/icon-512x512.png differ diff --git a/apps/web/public/images/logo-spinner-dark.gif b/apps/web/app/assets/images/logo-spinner-dark.gif similarity index 100% rename from apps/web/public/images/logo-spinner-dark.gif rename to apps/web/app/assets/images/logo-spinner-dark.gif diff --git a/apps/web/public/images/logo-spinner-light.gif b/apps/web/app/assets/images/logo-spinner-light.gif similarity index 100% rename from apps/web/public/images/logo-spinner-light.gif rename to apps/web/app/assets/images/logo-spinner-light.gif diff --git a/apps/web/public/instance-not-ready.webp b/apps/web/app/assets/instance-not-ready.webp similarity index 100% rename from apps/web/public/instance-not-ready.webp rename to apps/web/app/assets/instance-not-ready.webp diff --git a/apps/web/public/instance-setup-done.webp b/apps/web/app/assets/instance-setup-done.webp similarity index 100% rename from apps/web/public/instance-setup-done.webp rename to apps/web/app/assets/instance-setup-done.webp diff --git a/apps/web/public/instance/maintenance-mode-dark.svg b/apps/web/app/assets/instance/maintenance-mode-dark.svg similarity index 100% rename from apps/web/public/instance/maintenance-mode-dark.svg rename to apps/web/app/assets/instance/maintenance-mode-dark.svg diff --git a/apps/web/public/instance/maintenance-mode-light.svg b/apps/web/app/assets/instance/maintenance-mode-light.svg similarity index 100% rename from apps/web/public/instance/maintenance-mode-light.svg rename to apps/web/app/assets/instance/maintenance-mode-light.svg diff --git a/apps/web/public/logos/gitea-logo.svg b/apps/web/app/assets/logos/gitea-logo.svg similarity index 100% rename from apps/web/public/logos/gitea-logo.svg rename to apps/web/app/assets/logos/gitea-logo.svg diff --git a/apps/web/public/logos/github-black.png b/apps/web/app/assets/logos/github-black.png similarity index 100% rename from apps/web/public/logos/github-black.png rename to apps/web/app/assets/logos/github-black.png diff --git a/apps/web/public/logos/github-dark.svg b/apps/web/app/assets/logos/github-dark.svg similarity index 100% rename from apps/web/public/logos/github-dark.svg rename to apps/web/app/assets/logos/github-dark.svg diff --git a/apps/web/public/logos/github-square.png b/apps/web/app/assets/logos/github-square.png similarity index 100% rename from apps/web/public/logos/github-square.png rename to apps/web/app/assets/logos/github-square.png diff --git a/apps/web/public/logos/github-white.png b/apps/web/app/assets/logos/github-white.png similarity index 100% rename from apps/web/public/logos/github-white.png rename to apps/web/app/assets/logos/github-white.png diff --git a/apps/web/public/logos/gitlab-logo.svg b/apps/web/app/assets/logos/gitlab-logo.svg similarity index 100% rename from apps/web/public/logos/gitlab-logo.svg rename to apps/web/app/assets/logos/gitlab-logo.svg diff --git a/apps/web/public/logos/google-logo.svg b/apps/web/app/assets/logos/google-logo.svg similarity index 100% rename from apps/web/public/logos/google-logo.svg rename to apps/web/app/assets/logos/google-logo.svg diff --git a/apps/web/public/mac-command.svg b/apps/web/app/assets/mac-command.svg similarity index 100% rename from apps/web/public/mac-command.svg rename to apps/web/app/assets/mac-command.svg diff --git a/apps/web/public/og-image.png b/apps/web/app/assets/og-image.png similarity index 100% rename from apps/web/public/og-image.png rename to apps/web/app/assets/og-image.png diff --git a/apps/web/public/onboarding/cycles.webp b/apps/web/app/assets/onboarding/cycles.webp similarity index 100% rename from apps/web/public/onboarding/cycles.webp rename to apps/web/app/assets/onboarding/cycles.webp diff --git a/apps/web/public/onboarding/issues.webp b/apps/web/app/assets/onboarding/issues.webp similarity index 100% rename from apps/web/public/onboarding/issues.webp rename to apps/web/app/assets/onboarding/issues.webp diff --git a/apps/web/public/onboarding/modules.webp b/apps/web/app/assets/onboarding/modules.webp similarity index 100% rename from apps/web/public/onboarding/modules.webp rename to apps/web/app/assets/onboarding/modules.webp diff --git a/apps/web/public/onboarding/onboarding-pages.webp b/apps/web/app/assets/onboarding/onboarding-pages.webp similarity index 100% rename from apps/web/public/onboarding/onboarding-pages.webp rename to apps/web/app/assets/onboarding/onboarding-pages.webp diff --git a/apps/web/public/onboarding/pages.webp b/apps/web/app/assets/onboarding/pages.webp similarity index 100% rename from apps/web/public/onboarding/pages.webp rename to apps/web/app/assets/onboarding/pages.webp diff --git a/apps/web/public/onboarding/views.webp b/apps/web/app/assets/onboarding/views.webp similarity index 100% rename from apps/web/public/onboarding/views.webp rename to apps/web/app/assets/onboarding/views.webp diff --git a/apps/web/public/plane-logos/black-horizontal-with-blue-logo.png b/apps/web/app/assets/plane-logos/black-horizontal-with-blue-logo.png similarity index 100% rename from apps/web/public/plane-logos/black-horizontal-with-blue-logo.png rename to apps/web/app/assets/plane-logos/black-horizontal-with-blue-logo.png diff --git a/apps/web/public/plane-logos/blue-without-text.png b/apps/web/app/assets/plane-logos/blue-without-text.png similarity index 100% rename from apps/web/public/plane-logos/blue-without-text.png rename to apps/web/app/assets/plane-logos/blue-without-text.png diff --git a/apps/web/public/plane-logos/white-horizontal-with-blue-logo.png b/apps/web/app/assets/plane-logos/white-horizontal-with-blue-logo.png similarity index 100% rename from apps/web/public/plane-logos/white-horizontal-with-blue-logo.png rename to apps/web/app/assets/plane-logos/white-horizontal-with-blue-logo.png diff --git a/apps/web/public/plane-logos/white-horizontal.svg b/apps/web/app/assets/plane-logos/white-horizontal.svg similarity index 100% rename from apps/web/public/plane-logos/white-horizontal.svg rename to apps/web/app/assets/plane-logos/white-horizontal.svg diff --git a/apps/web/public/plane-takeoff.png b/apps/web/app/assets/plane-takeoff.png similarity index 100% rename from apps/web/public/plane-takeoff.png rename to apps/web/app/assets/plane-takeoff.png diff --git a/apps/web/public/services/csv.svg b/apps/web/app/assets/services/csv.svg similarity index 100% rename from apps/web/public/services/csv.svg rename to apps/web/app/assets/services/csv.svg diff --git a/apps/web/public/services/excel.svg b/apps/web/app/assets/services/excel.svg similarity index 100% rename from apps/web/public/services/excel.svg rename to apps/web/app/assets/services/excel.svg diff --git a/apps/web/public/services/github.png b/apps/web/app/assets/services/github.png similarity index 100% rename from apps/web/public/services/github.png rename to apps/web/app/assets/services/github.png diff --git a/apps/web/public/services/jira.svg b/apps/web/app/assets/services/jira.svg similarity index 100% rename from apps/web/public/services/jira.svg rename to apps/web/app/assets/services/jira.svg diff --git a/apps/web/public/services/json.svg b/apps/web/app/assets/services/json.svg similarity index 100% rename from apps/web/public/services/json.svg rename to apps/web/app/assets/services/json.svg diff --git a/apps/web/public/services/slack.png b/apps/web/app/assets/services/slack.png similarity index 100% rename from apps/web/public/services/slack.png rename to apps/web/app/assets/services/slack.png diff --git a/apps/web/public/user.png b/apps/web/app/assets/user.png similarity index 100% rename from apps/web/public/user.png rename to apps/web/app/assets/user.png diff --git a/apps/web/public/users/user-1.png b/apps/web/app/assets/users/user-1.png similarity index 100% rename from apps/web/public/users/user-1.png rename to apps/web/app/assets/users/user-1.png diff --git a/apps/web/public/users/user-2.png b/apps/web/app/assets/users/user-2.png similarity index 100% rename from apps/web/public/users/user-2.png rename to apps/web/app/assets/users/user-2.png diff --git a/apps/web/public/users/user-profile-cover-default-img.png b/apps/web/app/assets/users/user-profile-cover-default-img.png similarity index 100% rename from apps/web/public/users/user-profile-cover-default-img.png rename to apps/web/app/assets/users/user-profile-cover-default-img.png diff --git a/apps/web/public/workspace-active-cycles/cta-l-1-dark.webp b/apps/web/app/assets/workspace-active-cycles/cta-l-1-dark.webp similarity index 100% rename from apps/web/public/workspace-active-cycles/cta-l-1-dark.webp rename to apps/web/app/assets/workspace-active-cycles/cta-l-1-dark.webp diff --git a/apps/web/public/workspace-active-cycles/cta-l-1-light.webp b/apps/web/app/assets/workspace-active-cycles/cta-l-1-light.webp similarity index 100% rename from apps/web/public/workspace-active-cycles/cta-l-1-light.webp rename to apps/web/app/assets/workspace-active-cycles/cta-l-1-light.webp diff --git a/apps/web/public/workspace-active-cycles/cta-r-1-dark.webp b/apps/web/app/assets/workspace-active-cycles/cta-r-1-dark.webp similarity index 100% rename from apps/web/public/workspace-active-cycles/cta-r-1-dark.webp rename to apps/web/app/assets/workspace-active-cycles/cta-r-1-dark.webp diff --git a/apps/web/public/workspace-active-cycles/cta-r-1-light.webp b/apps/web/app/assets/workspace-active-cycles/cta-r-1-light.webp similarity index 100% rename from apps/web/public/workspace-active-cycles/cta-r-1-light.webp rename to apps/web/app/assets/workspace-active-cycles/cta-r-1-light.webp diff --git a/apps/web/public/workspace-active-cycles/cta-r-2-dark.webp b/apps/web/app/assets/workspace-active-cycles/cta-r-2-dark.webp similarity index 100% rename from apps/web/public/workspace-active-cycles/cta-r-2-dark.webp rename to apps/web/app/assets/workspace-active-cycles/cta-r-2-dark.webp diff --git a/apps/web/public/workspace-active-cycles/cta-r-2-light.webp b/apps/web/app/assets/workspace-active-cycles/cta-r-2-light.webp similarity index 100% rename from apps/web/public/workspace-active-cycles/cta-r-2-light.webp rename to apps/web/app/assets/workspace-active-cycles/cta-r-2-light.webp diff --git a/apps/web/public/workspace/workspace-creation-disabled.png b/apps/web/app/assets/workspace/workspace-creation-disabled.png similarity index 100% rename from apps/web/public/workspace/workspace-creation-disabled.png rename to apps/web/app/assets/workspace/workspace-creation-disabled.png diff --git a/apps/web/public/workspace/workspace-not-available.png b/apps/web/app/assets/workspace/workspace-not-available.png similarity index 100% rename from apps/web/public/workspace/workspace-not-available.png rename to apps/web/app/assets/workspace/workspace-not-available.png diff --git a/apps/web/app/compat/next/helper.ts b/apps/web/app/compat/next/helper.ts new file mode 100644 index 000000000..fe1a98446 --- /dev/null +++ b/apps/web/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/web/app/compat/next/image.tsx b/apps/web/app/compat/next/image.tsx new file mode 100644 index 000000000..f9a858db2 --- /dev/null +++ b/apps/web/app/compat/next/image.tsx @@ -0,0 +1,33 @@ +"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; + fill?: boolean; + priority?: boolean; + quality?: number; + placeholder?: "blur" | "empty"; + blurDataURL?: string; +}; + +const Image: React.FC = ({ + src, + alt = "", + fill, + priority: _priority, + quality: _quality, + placeholder: _placeholder, + blurDataURL: _blurDataURL, + ...rest +}) => { + // If fill is true, apply object-fit styles + const style = fill ? { objectFit: "cover" as const, width: "100%", height: "100%" } : rest.style; + + return {alt}; +}; + +export default Image; diff --git a/apps/web/app/compat/next/link.tsx b/apps/web/app/compat/next/link.tsx new file mode 100644 index 000000000..4f4236327 --- /dev/null +++ b/apps/web/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/web/app/compat/next/navigation.ts b/apps/web/app/compat/next/navigation.ts new file mode 100644 index 000000000..ecb076fed --- /dev/null +++ b/apps/web/app/compat/next/navigation.ts @@ -0,0 +1,48 @@ +"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) => { + // Defer navigation to avoid state updates during render + setTimeout(() => navigate(ensureTrailingSlash(to)), 0); + }, + replace: (to: string) => { + // Defer navigation to avoid state updates during render + setTimeout(() => navigate(ensureTrailingSlash(to), { replace: true }), 0); + }, + back: () => { + setTimeout(() => navigate(-1), 0); + }, + forward: () => { + setTimeout(() => navigate(1), 0); + }, + 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/web/app/compat/next/script.tsx b/apps/web/app/compat/next/script.tsx new file mode 100644 index 000000000..2baa63f45 --- /dev/null +++ b/apps/web/app/compat/next/script.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { useEffect } from "react"; + +type ScriptProps = { + src?: string; + id?: string; + strategy?: "beforeInteractive" | "afterInteractive" | "lazyOnload" | "worker"; + onLoad?: () => void; + onError?: () => void; + children?: string; + defer?: boolean; + [key: string]: any; +}; + +// Minimal shim for next/script that creates a script tag +const Script: React.FC = ({ src, id, strategy: _strategy, onLoad, onError, children, ...rest }) => { + useEffect(() => { + if (src) { + const script = document.createElement("script"); + if (id) script.id = id; + script.src = src; + if (onLoad) script.onload = onLoad; + if (onError) script.onerror = onError; + Object.keys(rest).forEach((key) => { + script.setAttribute(key, rest[key]); + }); + document.body.appendChild(script); + + return () => { + if (script.parentNode) { + document.body.removeChild(script); + } + }; + } else if (children) { + const script = document.createElement("script"); + if (id) script.id = id; + script.textContent = children; + Object.keys(rest).forEach((key) => { + script.setAttribute(key, rest[key]); + }); + document.body.appendChild(script); + + return () => { + if (script.parentNode) { + document.body.removeChild(script); + } + }; + } + }, [src, id, children, onLoad, onError, rest]); + + return null; +}; + +export default Script; diff --git a/apps/web/app/error/dev.tsx b/apps/web/app/error/dev.tsx new file mode 100644 index 000000000..d4c30165b --- /dev/null +++ b/apps/web/app/error/dev.tsx @@ -0,0 +1,155 @@ +"use client"; + +// plane imports +import { isRouteErrorResponse } from "react-router"; +import { Banner } from "@plane/propel/banner"; +import { Button } from "@plane/propel/button"; +import { Card, ECardVariant } from "@plane/propel/card"; +import { InfoFillIcon } from "@plane/propel/icons"; + +interface ErrorActionsProps { + onGoHome: () => void; + onReload?: () => void; +} + +const ErrorActions: React.FC = ({ onGoHome, onReload }) => ( +
+ + {onReload && ( + + )} +
+); + +interface DevErrorComponentProps { + error: unknown; + onGoHome: () => void; + onReload: () => void; +} + +export const DevErrorComponent: React.FC = ({ error, onGoHome, onReload }) => { + if (isRouteErrorResponse(error)) { + return ( +
+
+ } + title="Route Error Response" + animationDuration={0} + /> + + +
+
+

+ {error.status} {error.statusText} +

+
+
+ +
+

Error Data

+
+

{error.data}

+
+
+ + +
+ +
+
+ ); + } + + if (error instanceof Error) { + return ( +
+
+ } + title="Runtime Error" + animationDuration={0} + /> + +
+
+

Error

+
+
+ +
+

Message

+
+

{error.message}

+
+
+ + {error.stack && ( +
+

Stack Trace

+
+
+                      {error.stack}
+                    
+
+
+ )} + + +
+ + + +
+ +
+

Development Mode

+

+ This detailed error view is only visible in development. In production, users will see a friendly + error page. +

+
+
+
+
+
+ ); + } + + return ( +
+
+ } + title="Unknown Error" + animationDuration={0} + /> + + +
+
+

Unknown Error

+
+
+ +
+

+ An unknown error occurred. Please try refreshing the page or contact support if the problem persists. +

+
+ + +
+ +
+
+ ); +}; diff --git a/apps/web/app/error/index.tsx b/apps/web/app/error/index.tsx new file mode 100644 index 000000000..b577f866b --- /dev/null +++ b/apps/web/app/error/index.tsx @@ -0,0 +1,21 @@ +"use client"; + +// hooks +import { useAppRouter } from "@/hooks/use-app-router"; +// layouts +import { DevErrorComponent } from "./dev"; +import { ProdErrorComponent } from "./prod"; + +export const CustomErrorComponent: React.FC<{ error: unknown }> = ({ error }) => { + // router + const router = useAppRouter(); + + const handleGoHome = () => router.push("/"); + const handleReload = () => window.location.reload(); + + if (import.meta.env.DEV) { + return ; + } + + return ; +}; diff --git a/apps/web/app/error.tsx b/apps/web/app/error/prod.tsx similarity index 83% rename from apps/web/app/error.tsx rename to apps/web/app/error/prod.tsx index a6fa660a7..e30c58bdd 100644 --- a/apps/web/app/error.tsx +++ b/apps/web/app/error/prod.tsx @@ -1,14 +1,13 @@ "use client"; -import Image from "next/image"; import { useTheme } from "next-themes"; -// layouts +// plane imports import { Button } from "@plane/propel/button"; -import { useAppRouter } from "@/hooks/use-app-router"; +// assets +import maintenanceModeDarkModeImage from "@/app/assets/instance/maintenance-mode-dark.svg?url"; +import maintenanceModeLightModeImage from "@/app/assets/instance/maintenance-mode-light.svg?url"; +// layouts import DefaultLayout from "@/layouts/default-layout"; -// images -import maintenanceModeDarkModeImage from "@/public/instance/maintenance-mode-dark.svg"; -import maintenanceModeLightModeImage from "@/public/instance/maintenance-mode-light.svg"; const linkMap = [ { @@ -28,10 +27,14 @@ const linkMap = [ }, ]; -export default function CustomErrorComponent() { +// Production Error Component +interface ProdErrorComponentProps { + onGoHome: () => void; +} + +export const ProdErrorComponent: React.FC = ({ onGoHome }) => { // hooks const { resolvedTheme } = useTheme(); - const router = useAppRouter(); // derived values const maintenanceModeImage = resolvedTheme === "dark" ? maintenanceModeDarkModeImage : maintenanceModeLightModeImage; @@ -40,7 +43,7 @@ export default function CustomErrorComponent() {
-
-
@@ -83,4 +86,4 @@ export default function CustomErrorComponent() {
); -} +}; diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx deleted file mode 100644 index b0b191e41..000000000 --- a/apps/web/app/global-error.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -import NextError from "next/error"; - -export default function GlobalError() { - return ( - - - {/* `NextError` is the default Next.js error page component. Its type - definition requires a `statusCode` prop. However, since the App Router - does not expose status codes for errors, we simply pass 0 to render a - generic error message. */} - - - - ); -} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index b2b274c73..d73d82d0d 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,4 +1,3 @@ -import type { Metadata, Viewport } from "next"; import Script from "next/script"; // styles @@ -9,50 +8,46 @@ import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants"; // helpers import { cn } from "@plane/utils"; +// assets +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"; +import icon180 from "@/app/assets/icons/icon-180x180.png?url"; +import icon512 from "@/app/assets/icons/icon-512x512.png?url"; + // local import { AppProvider } from "./provider"; -export const metadata: Metadata = { - title: "Plane | Simple, extensible, open-source project management tool.", - description: SITE_DESCRIPTION, - metadataBase: new URL("https://app.plane.so"), - openGraph: { - title: "Plane | Simple, extensible, open-source project management tool.", - description: "Open-source project management tool to manage work items, cycles, and product roadmaps easily", - url: "https://app.plane.so/", - images: [ - { - url: "/og-image.png", - width: 1200, - height: 630, - alt: "Plane - Modern project management", - }, - ], +export const meta = () => [ + { title: "Plane | Simple, extensible, open-source project management tool." }, + { name: "description", content: SITE_DESCRIPTION }, + { + name: "keywords", + content: + "software development, plan, ship, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration", }, - keywords: - "software development, plan, ship, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration", - twitter: { - site: "@planepowers", - card: "summary_large_image", - images: [ - { - url: "/og-image.png", - width: 1200, - height: 630, - alt: "Plane - Modern project management", - }, - ], + { + name: "viewport", + content: + "width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover", }, -}; - -export const viewport: Viewport = { - minimumScale: 1, - initialScale: 1, - maximumScale: 1, - userScalable: false, - width: "device-width", - viewportFit: "cover", -}; + { property: "og:title", content: "Plane | Simple, extensible, open-source project management tool." }, + { + property: "og:description", + content: "Open-source project management tool to manage work items, cycles, and product roadmaps easily", + }, + { property: "og:url", content: "https://app.plane.so/" }, + { property: "og:image", content: "https://app.plane.so/og-image.png" }, + { property: "og:image:width", content: "1200" }, + { property: "og:image:height", content: "630" }, + { property: "og:image:alt", content: "Plane - Modern project management" }, + { name: "twitter:site", content: "@planepowers" }, + { name: "twitter:card", content: "summary_large_image" }, + { name: "twitter:image", content: "https://app.plane.so/og-image.png" }, + { name: "twitter:image:width", content: "1200" }, + { name: "twitter:image:height", content: "630" }, + { name: "twitter:image:alt", content: "Plane - Modern project management" }, +]; export default function RootLayout({ children }: { children: React.ReactNode }) { const isSessionRecorderEnabled = parseInt(process.env.NEXT_PUBLIC_ENABLE_SESSION_RECORDER || "0"); @@ -61,10 +56,10 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - - + + - + {/* Meta info for PWA */} @@ -72,10 +67,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - - - - + + + diff --git a/apps/web/app/not-found.tsx b/apps/web/app/not-found.tsx index 3d58991ba..e7a817834 100644 --- a/apps/web/app/not-found.tsx +++ b/apps/web/app/not-found.tsx @@ -1,28 +1,25 @@ "use client"; -import React from "react"; -import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; // ui import { Button } from "@plane/propel/button"; // images -import Image404 from "@/public/404.svg"; +import Image404 from "@/app/assets/404.svg?url"; +// types +import type { Route } from "./+types/not-found"; -export const metadata: Metadata = { - title: "404 - Page Not Found", - robots: { - index: false, - follow: false, - }, -}; +export const meta: Route.MetaFunction = () => [ + { title: "404 - Page Not Found" }, + { name: "robots", content: "noindex, nofollow" }, +]; const PageNotFound = () => (
- 404- Page not found + 404- Page not found

Oops! Something went wrong.

diff --git a/apps/web/app/provider.tsx b/apps/web/app/provider.tsx index a83c75f9b..125f50216 100644 --- a/apps/web/app/provider.tsx +++ b/apps/web/app/provider.tsx @@ -1,26 +1,27 @@ "use client"; import type { FC, ReactNode } from "react"; -import { AppProgressProvider as ProgressProvider } from "@bprogress/next"; -import dynamic from "next/dynamic"; +import { lazy, Suspense } from "react"; import { useTheme, ThemeProvider } from "next-themes"; import { SWRConfig } from "swr"; // Plane Imports import { WEB_SWR_CONFIG } from "@plane/constants"; import { TranslationProvider } from "@plane/i18n"; import { Toast } from "@plane/propel/toast"; -//helpers +// helpers 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"; -// dynamic imports -const StoreWrapper = dynamic(() => import("@/lib/wrappers/store-wrapper"), { ssr: false }); -const PostHogProvider = dynamic(() => import("@/lib/posthog-provider"), { ssr: false }); -const IntercomProvider = dynamic(() => import("@/lib/intercom-provider"), { ssr: false }); +// lazy imports +const StoreWrapper = lazy(() => import("@/lib/wrappers/store-wrapper")); +const PostHogProvider = lazy(() => import("@/lib/posthog-provider")); +const IntercomProvider = lazy(() => import("@/lib/intercom-provider")); export interface IAppProvider { children: ReactNode; @@ -35,30 +36,24 @@ export const AppProvider: FC = (props) => { const { children } = props; // themes return ( - <> - - - - - - - - - - {children} - - - - - - - - - + + + + + + + + + + + {children} + + + + + + + + ); }; diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx new file mode 100644 index 000000000..c37fe6f60 --- /dev/null +++ b/apps/web/app/root.tsx @@ -0,0 +1,126 @@ +import type { ReactNode } from "react"; +import Script from "next/script"; +import { Links, Meta, Outlet, Scripts } from "react-router"; +import type { LinksFunction } from "react-router"; +// plane imports +import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants"; +import { cn } from "@plane/utils"; +// types +// assets +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"; +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 globalStyles from "@/styles/globals.css?url"; +import type { Route } from "./+types/root"; +// local +import { CustomErrorComponent } from "./error"; +import { AppProvider } from "./provider"; + +const APP_TITLE = "Plane | Simple, extensible, open-source project management tool."; + +export const links: LinksFunction = () => [ + { 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" }, + { rel: "apple-touch-icon", href: icon512 }, + { rel: "apple-touch-icon", sizes: "180x180", href: icon180 }, + { rel: "apple-touch-icon", sizes: "512x512", href: icon512 }, + { rel: "manifest", href: "/manifest.json" }, + { rel: "stylesheet", href: globalStyles }, +]; + +export function Layout({ children }: { children: ReactNode }) { + const isSessionRecorderEnabled = parseInt(process.env.NEXT_PUBLIC_ENABLE_SESSION_RECORDER || "0"); + + return ( + + + + + + {/* Meta info for PWA */} + + + + + + + + + + +
+
+ +
+
{children}
+
+
+ + {process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN && ( + + )} + + + ); +} + +export const meta: Route.MetaFunction = () => [ + { title: APP_TITLE }, + { name: "description", content: SITE_DESCRIPTION }, + { property: "og:title", content: APP_TITLE }, + { + property: "og:description", + content: "Open-source project management tool to manage work items, cycles, and product roadmaps easily", + }, + { property: "og:url", content: "https://app.plane.so/" }, + { property: "og:image", content: ogImage }, + { property: "og:image:width", content: "1200" }, + { property: "og:image:height", content: "630" }, + { property: "og:image:alt", content: "Plane - Modern project management" }, + { + name: "keywords", + content: + "software development, plan, ship, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration", + }, + { name: "twitter:site", content: "@planepowers" }, + { name: "twitter:card", content: "summary_large_image" }, + { name: "twitter:image", content: ogImage }, + { name: "twitter:image:width", content: "1200" }, + { name: "twitter:image:height", content: "630" }, + { name: "twitter:image:alt", content: "Plane - Modern project management" }, +]; + +export default function Root() { + return ; +} + +export function HydrateFallback() { + return null; +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + return ; +} diff --git a/apps/web/app/routes.ts b/apps/web/app/routes.ts new file mode 100644 index 000000000..258993783 --- /dev/null +++ b/apps/web/app/routes.ts @@ -0,0 +1,84 @@ +import { route } from "@react-router/dev/routes"; +import type { RouteConfigEntry } from "@react-router/dev/routes"; +import { coreRoutes } from "./routes/core"; +import { extendedRoutes } from "./routes/extended"; + +/** + * Merges two route configurations intelligently. + * - Deep merges children when the same layout file exists in both arrays + * - Deduplicates routes by file property, preferring extended over core + * - Maintains order: core routes first, then extended routes at each level + */ +function mergeRoutes(core: RouteConfigEntry[], extended: RouteConfigEntry[]): RouteConfigEntry[] { + // Step 1: Create a Map to track routes by file path + const routeMap = new Map(); + + // Step 2: Process core routes first + for (const coreRoute of core) { + const fileKey = coreRoute.file; + routeMap.set(fileKey, coreRoute); + } + + // Step 3: Process extended routes + for (const extendedRoute of extended) { + const fileKey = extendedRoute.file; + + if (routeMap.has(fileKey)) { + // Route exists in both - need to merge + const coreRoute = routeMap.get(fileKey)!; + + // Check if both have children (layouts that need deep merging) + if (coreRoute.children && extendedRoute.children) { + // Deep merge: recursively merge children + const mergedChildren = mergeRoutes( + Array.isArray(coreRoute.children) ? coreRoute.children : [], + Array.isArray(extendedRoute.children) ? extendedRoute.children : [] + ); + routeMap.set(fileKey, { + ...extendedRoute, + children: mergedChildren, + }); + } else { + // No children or only one has children - prefer extended + routeMap.set(fileKey, extendedRoute); + } + } else { + // Route only exists in extended + routeMap.set(fileKey, extendedRoute); + } + } + + // Step 4: Build final array maintaining order (core first, then extended-only) + const result: RouteConfigEntry[] = []; + + // Add all core routes (now merged or original) + for (const coreRoute of core) { + const fileKey = coreRoute.file; + if (routeMap.has(fileKey)) { + result.push(routeMap.get(fileKey)!); + routeMap.delete(fileKey); // Remove so we don't add it again + } + } + + // Add remaining extended-only routes + for (const extendedRoute of extended) { + const fileKey = extendedRoute.file; + if (routeMap.has(fileKey)) { + result.push(routeMap.get(fileKey)!); + routeMap.delete(fileKey); + } + } + + return result; +} + +/** + * Main Routes Configuration + * This file serves as the entry point for the route configuration. + */ +const mergedRoutes: RouteConfigEntry[] = mergeRoutes(coreRoutes, extendedRoutes); + +// Add catch-all route at the end (404 handler) +const routes: RouteConfigEntry[] = [...mergedRoutes, route("*", "./not-found.tsx")]; + +export default routes; diff --git a/apps/web/app/routes/core.ts b/apps/web/app/routes/core.ts new file mode 100644 index 000000000..e502a4e68 --- /dev/null +++ b/apps/web/app/routes/core.ts @@ -0,0 +1,429 @@ +import { index, layout, route } from "@react-router/dev/routes"; +import type { RouteConfig, RouteConfigEntry } from "@react-router/dev/routes"; + +export const coreRoutes: RouteConfigEntry[] = [ + // ======================================================================== + // USER MANAGEMENT ROUTES + // ======================================================================== + + // Home - Sign In + layout("./(home)/layout.tsx", [index("./(home)/page.tsx")]), + + // Sign Up + layout("./(all)/sign-up/layout.tsx", [route("sign-up", "./(all)/sign-up/page.tsx")]), + + // Account Routes - Password Management + layout("./(all)/accounts/forgot-password/layout.tsx", [ + route("accounts/forgot-password", "./(all)/accounts/forgot-password/page.tsx"), + ]), + layout("./(all)/accounts/reset-password/layout.tsx", [ + route("accounts/reset-password", "./(all)/accounts/reset-password/page.tsx"), + ]), + layout("./(all)/accounts/set-password/layout.tsx", [ + route("accounts/set-password", "./(all)/accounts/set-password/page.tsx"), + ]), + + // Create Workspace + layout("./(all)/create-workspace/layout.tsx", [route("create-workspace", "./(all)/create-workspace/page.tsx")]), + + // Onboarding + layout("./(all)/onboarding/layout.tsx", [route("onboarding", "./(all)/onboarding/page.tsx")]), + + // Invitations + layout("./(all)/invitations/layout.tsx", [route("invitations", "./(all)/invitations/page.tsx")]), + + // Workspace Invitations + layout("./(all)/workspace-invitations/layout.tsx", [ + route("workspace-invitations", "./(all)/workspace-invitations/page.tsx"), + ]), + + // ======================================================================== + // ALL APP ROUTES + // ======================================================================== + layout("./(all)/layout.tsx", [ + // ====================================================================== + // WORKSPACE-SCOPED ROUTES + // ====================================================================== + layout("./(all)/[workspaceSlug]/layout.tsx", [ + // ==================================================================== + // PROJECTS APP SECTION - WORKSPACE LEVEL ROUTES + // ==================================================================== + layout("./(all)/[workspaceSlug]/(projects)/layout.tsx", [ + // -------------------------------------------------------------------- + // WORKSPACE LEVEL ROUTES + // -------------------------------------------------------------------- + + // Workspace Home + route(":workspaceSlug", "./(all)/[workspaceSlug]/(projects)/page.tsx"), + + // Active Cycles + layout("./(all)/[workspaceSlug]/(projects)/active-cycles/layout.tsx", [ + route(":workspaceSlug/active-cycles", "./(all)/[workspaceSlug]/(projects)/active-cycles/page.tsx"), + ]), + + // Analytics + layout("./(all)/[workspaceSlug]/(projects)/analytics/[tabId]/layout.tsx", [ + route(":workspaceSlug/analytics/:tabId", "./(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx"), + ]), + + // Browse + layout("./(all)/[workspaceSlug]/(projects)/browse/[workItem]/layout.tsx", [ + route(":workspaceSlug/browse/:workItem", "./(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx"), + ]), + + // Drafts + layout("./(all)/[workspaceSlug]/(projects)/drafts/layout.tsx", [ + route(":workspaceSlug/drafts", "./(all)/[workspaceSlug]/(projects)/drafts/page.tsx"), + ]), + + // Notifications + layout("./(all)/[workspaceSlug]/(projects)/notifications/layout.tsx", [ + route(":workspaceSlug/notifications", "./(all)/[workspaceSlug]/(projects)/notifications/page.tsx"), + ]), + + // Profile + layout("./(all)/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx", [ + route(":workspaceSlug/profile/:userId", "./(all)/[workspaceSlug]/(projects)/profile/[userId]/page.tsx"), + route( + ":workspaceSlug/profile/:userId/:profileViewId", + "./(all)/[workspaceSlug]/(projects)/profile/[userId]/[profileViewId]/page.tsx" + ), + route( + ":workspaceSlug/profile/:userId/activity", + "./(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx" + ), + ]), + + // Stickies + layout("./(all)/[workspaceSlug]/(projects)/stickies/layout.tsx", [ + route(":workspaceSlug/stickies", "./(all)/[workspaceSlug]/(projects)/stickies/page.tsx"), + ]), + + // Workspace Views + layout("./(all)/[workspaceSlug]/(projects)/workspace-views/layout.tsx", [ + route(":workspaceSlug/workspace-views", "./(all)/[workspaceSlug]/(projects)/workspace-views/page.tsx"), + route( + ":workspaceSlug/workspace-views/:globalViewId", + "./(all)/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx" + ), + ]), + + // ==================================================================== + // PROJECT LEVEL ROUTES + // ==================================================================== + + // -------------------------------------------------------------------- + // PROJECT LEVEL ROUTES + // -------------------------------------------------------------------- + + // Project List + layout("./(all)/[workspaceSlug]/(projects)/projects/(list)/layout.tsx", [ + route(":workspaceSlug/projects", "./(all)/[workspaceSlug]/(projects)/projects/(list)/page.tsx"), + ]), + + // Project Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx", [ + // Archived Projects + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/layout.tsx", [ + route( + ":workspaceSlug/projects/archives", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/archives/page.tsx" + ), + ]), + + // Project Issues + // Issues List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/issues", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx" + ), + ]), + + // Issue Detail + route( + ":workspaceSlug/projects/:projectId/issues/:issueId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx" + ), + + // Project Cycles + // Cycles List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/cycles", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx" + ), + ]), + + // Cycle Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/cycles/:cycleId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx" + ), + ]), + + // Project Modules + // Modules List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/modules", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx" + ), + ]), + + // Module Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/modules/:moduleId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx" + ), + ]), + + // Project Views + // Views List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/views", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx" + ), + ]), + + // View Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/views/:viewId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/page.tsx" + ), + ]), + + // Project Pages + // Pages List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/pages", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx" + ), + ]), + + // Page Detail + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/pages/:pageId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx" + ), + ]), + + // Project Intake + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/intake", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/intake/page.tsx" + ), + ]), + + // Project Archives - Issues, Cycles, Modules + // Project Archives - Issues - List + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/issues", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx" + ), + ]), + + // Project Archives - Issues - Detail + layout( + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/layout.tsx", + [ + route( + ":workspaceSlug/projects/:projectId/archives/issues/:archivedIssueId", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx" + ), + ] + ), + + // Project Archives - Cycles + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/cycles", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/cycles/page.tsx" + ), + ]), + + // Project Archives - Modules + layout("./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/layout.tsx", [ + route( + ":workspaceSlug/projects/:projectId/archives/modules", + "./(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/modules/page.tsx" + ), + ]), + ]), + ]), + + // ==================================================================== + // SETTINGS SECTION + // ==================================================================== + layout("./(all)/[workspaceSlug]/(settings)/layout.tsx", [ + // -------------------------------------------------------------------- + // WORKSPACE SETTINGS + // -------------------------------------------------------------------- + + layout("./(all)/[workspaceSlug]/(settings)/settings/(workspace)/layout.tsx", [ + route(":workspaceSlug/settings", "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/page.tsx"), + route( + ":workspaceSlug/settings/members", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx" + ), + route( + ":workspaceSlug/settings/billing", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/billing/page.tsx" + ), + route( + ":workspaceSlug/settings/integrations", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/integrations/page.tsx" + ), + route( + ":workspaceSlug/settings/imports", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/imports/page.tsx" + ), + route( + ":workspaceSlug/settings/exports", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/exports/page.tsx" + ), + route( + ":workspaceSlug/settings/webhooks", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx" + ), + route( + ":workspaceSlug/settings/webhooks/:webhookId", + "./(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/[webhookId]/page.tsx" + ), + ]), + + // -------------------------------------------------------------------- + // ACCOUNT SETTINGS + // -------------------------------------------------------------------- + + layout("./(all)/[workspaceSlug]/(settings)/settings/account/layout.tsx", [ + route(":workspaceSlug/settings/account", "./(all)/[workspaceSlug]/(settings)/settings/account/page.tsx"), + route( + ":workspaceSlug/settings/account/activity", + "./(all)/[workspaceSlug]/(settings)/settings/account/activity/page.tsx" + ), + route( + ":workspaceSlug/settings/account/preferences", + "./(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx" + ), + route( + ":workspaceSlug/settings/account/notifications", + "./(all)/[workspaceSlug]/(settings)/settings/account/notifications/page.tsx" + ), + route( + ":workspaceSlug/settings/account/security", + "./(all)/[workspaceSlug]/(settings)/settings/account/security/page.tsx" + ), + route( + ":workspaceSlug/settings/account/api-tokens", + "./(all)/[workspaceSlug]/(settings)/settings/account/api-tokens/page.tsx" + ), + ]), + + // -------------------------------------------------------------------- + // PROJECT SETTINGS + // -------------------------------------------------------------------- + + layout("./(all)/[workspaceSlug]/(settings)/settings/projects/layout.tsx", [ + // CORE Routes + // Project Settings + route(":workspaceSlug/settings/projects", "./(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx"), + route( + ":workspaceSlug/settings/projects/:projectId", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx" + ), + // Project Members + route( + ":workspaceSlug/settings/projects/:projectId/members", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/members/page.tsx" + ), + // Project Features + route( + ":workspaceSlug/settings/projects/:projectId/features", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/features/page.tsx" + ), + // Project States + route( + ":workspaceSlug/settings/projects/:projectId/states", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/states/page.tsx" + ), + // Project Labels + route( + ":workspaceSlug/settings/projects/:projectId/labels", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/labels/page.tsx" + ), + // Project Estimates + route( + ":workspaceSlug/settings/projects/:projectId/estimates", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/estimates/page.tsx" + ), + // Project Automations + layout("./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/layout.tsx", [ + route( + ":workspaceSlug/settings/projects/:projectId/automations", + "./(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/automations/page.tsx" + ), + ]), + ]), + ]), + ]), + // ====================================================================== + // STANDALONE ROUTES (outside workspace context) + // ====================================================================== + + // -------------------------------------------------------------------- + // PROFILE SETTINGS + // -------------------------------------------------------------------- + + layout("./(all)/profile/layout.tsx", [ + route("profile", "./(all)/profile/page.tsx"), + route("profile/activity", "./(all)/profile/activity/page.tsx"), + route("profile/appearance", "./(all)/profile/appearance/page.tsx"), + route("profile/notifications", "./(all)/profile/notifications/page.tsx"), + route("profile/security", "./(all)/profile/security/page.tsx"), + ]), + ]), + + // ======================================================================== + // REDIRECT ROUTES + // ======================================================================== + // Legacy URL redirects for backward compatibility + + // -------------------------------------------------------------------- + // REDIRECT ROUTES + // -------------------------------------------------------------------- + + // Project settings redirect: /:workspaceSlug/projects/:projectId/settings/:path* + // → /:workspaceSlug/settings/projects/:projectId/:path* + route(":workspaceSlug/projects/:projectId/settings/*", "routes/redirects/core/project-settings.tsx"), + + // Analytics redirect: /:workspaceSlug/analytics → /:workspaceSlug/analytics/overview + route(":workspaceSlug/analytics", "routes/redirects/core/analytics.tsx"), + + // API tokens redirect: /:workspaceSlug/settings/api-tokens + // → /:workspaceSlug/settings/account/api-tokens + route(":workspaceSlug/settings/api-tokens", "routes/redirects/core/api-tokens.tsx"), + + // Inbox redirect: /:workspaceSlug/projects/:projectId/inbox + // → /:workspaceSlug/projects/:projectId/intake + route(":workspaceSlug/projects/:projectId/inbox", "routes/redirects/core/inbox.tsx"), + + // Sign-up redirects + route("accounts/sign-up", "routes/redirects/core/accounts-signup.tsx"), + + // Sign-in redirects (all redirect to home page) + route("sign-in", "routes/redirects/core/sign-in.tsx"), + route("signin", "routes/redirects/core/signin.tsx"), + route("login", "routes/redirects/core/login.tsx"), + + // Register redirect + route("register", "routes/redirects/core/register.tsx"), +] satisfies RouteConfig; diff --git a/apps/web/app/routes/extended.ts b/apps/web/app/routes/extended.ts new file mode 100644 index 000000000..bbc5aa4cc --- /dev/null +++ b/apps/web/app/routes/extended.ts @@ -0,0 +1,3 @@ +import type { RouteConfigEntry } from "@react-router/dev/routes"; + +export const extendedRoutes: RouteConfigEntry[] = []; diff --git a/apps/web/app/routes/redirects/core/accounts-signup.tsx b/apps/web/app/routes/redirects/core/accounts-signup.tsx new file mode 100644 index 000000000..5343e27be --- /dev/null +++ b/apps/web/app/routes/redirects/core/accounts-signup.tsx @@ -0,0 +1,9 @@ +import { redirect } from "react-router"; + +export const clientLoader = () => { + throw redirect("/sign-up/"); +}; + +export default function AccountsSignup() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/analytics.tsx b/apps/web/app/routes/redirects/core/analytics.tsx new file mode 100644 index 000000000..21bacf509 --- /dev/null +++ b/apps/web/app/routes/redirects/core/analytics.tsx @@ -0,0 +1,11 @@ +import { redirect } from "react-router"; +import type { Route } from "./+types/analytics"; + +export const clientLoader = ({ params }: Route.ClientLoaderArgs) => { + const { workspaceSlug } = params; + throw redirect(`/${workspaceSlug}/analytics/overview/`); +}; + +export default function Analytics() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/api-tokens.tsx b/apps/web/app/routes/redirects/core/api-tokens.tsx new file mode 100644 index 000000000..68007aa41 --- /dev/null +++ b/apps/web/app/routes/redirects/core/api-tokens.tsx @@ -0,0 +1,11 @@ +import { redirect } from "react-router"; +import type { Route } from "./+types/api-tokens"; + +export const clientLoader = ({ params }: Route.ClientLoaderArgs) => { + const { workspaceSlug } = params; + throw redirect(`/${workspaceSlug}/settings/account/api-tokens/`); +}; + +export default function ApiTokens() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/inbox.tsx b/apps/web/app/routes/redirects/core/inbox.tsx new file mode 100644 index 000000000..bf2bd9a50 --- /dev/null +++ b/apps/web/app/routes/redirects/core/inbox.tsx @@ -0,0 +1,11 @@ +import { redirect } from "react-router"; +import type { Route } from "./+types/inbox"; + +export const clientLoader = ({ params }: Route.ClientLoaderArgs) => { + const { workspaceSlug, projectId } = params; + throw redirect(`/${workspaceSlug}/projects/${projectId}/intake/`); +}; + +export default function Inbox() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/index.ts b/apps/web/app/routes/redirects/core/index.ts new file mode 100644 index 000000000..efd3ae40f --- /dev/null +++ b/apps/web/app/routes/redirects/core/index.ts @@ -0,0 +1,38 @@ +import { route } from "@react-router/dev/routes"; +import type { RouteConfigEntry } from "@react-router/dev/routes"; + +export const coreRedirectRoutes: RouteConfigEntry[] = [ + // ======================================================================== + // WORKSPACE & PROJECT REDIRECTS + // ======================================================================== + + // Project settings redirect: /:workspaceSlug/projects/:projectId/settings/:path* + // → /:workspaceSlug/settings/projects/:projectId/:path* + route(":workspaceSlug/projects/:projectId/settings/*", "routes/redirects/core/project-settings.tsx"), + + // Analytics redirect: /:workspaceSlug/analytics → /:workspaceSlug/analytics/overview + route(":workspaceSlug/analytics", "routes/redirects/core/analytics.tsx"), + + // API tokens redirect: /:workspaceSlug/settings/api-tokens + // → /:workspaceSlug/settings/account/api-tokens + route(":workspaceSlug/settings/api-tokens", "routes/redirects/core/api-tokens.tsx"), + + // Inbox redirect: /:workspaceSlug/projects/:projectId/inbox + // → /:workspaceSlug/projects/:projectId/intake + route(":workspaceSlug/projects/:projectId/inbox", "routes/redirects/core/inbox.tsx"), + + // ======================================================================== + // AUTHENTICATION REDIRECTS + // ======================================================================== + + // Sign-up redirects + route("accounts/sign-up", "routes/redirects/core/accounts-signup.tsx"), + + // Sign-in redirects (all redirect to home page) + route("sign-in", "routes/redirects/core/sign-in.tsx"), + route("signin", "routes/redirects/core/signin.tsx"), + route("login", "routes/redirects/core/login.tsx"), + + // Register redirect + route("register", "routes/redirects/core/register.tsx"), +]; diff --git a/apps/web/app/routes/redirects/core/login.tsx b/apps/web/app/routes/redirects/core/login.tsx new file mode 100644 index 000000000..ed49c8ca3 --- /dev/null +++ b/apps/web/app/routes/redirects/core/login.tsx @@ -0,0 +1,9 @@ +import { redirect } from "react-router"; + +export const clientLoader = () => { + throw redirect("/"); +}; + +export default function Login() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/project-settings.tsx b/apps/web/app/routes/redirects/core/project-settings.tsx new file mode 100644 index 000000000..da14c515a --- /dev/null +++ b/apps/web/app/routes/redirects/core/project-settings.tsx @@ -0,0 +1,13 @@ +import { redirect } from "react-router"; +import type { Route } from "./+types/project-settings"; + +export const clientLoader = ({ params }: Route.ClientLoaderArgs) => { + const { workspaceSlug, projectId } = params; + const splat = params["*"] || ""; + const destination = `/${workspaceSlug}/settings/projects/${projectId}${splat ? `/${splat}` : ""}/`; + throw redirect(destination); +}; + +export default function ProjectSettings() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/register.tsx b/apps/web/app/routes/redirects/core/register.tsx new file mode 100644 index 000000000..791040495 --- /dev/null +++ b/apps/web/app/routes/redirects/core/register.tsx @@ -0,0 +1,9 @@ +import { redirect } from "react-router"; + +export const clientLoader = () => { + throw redirect("/sign-up/"); +}; + +export default function Register() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/sign-in.tsx b/apps/web/app/routes/redirects/core/sign-in.tsx new file mode 100644 index 000000000..83a91a3eb --- /dev/null +++ b/apps/web/app/routes/redirects/core/sign-in.tsx @@ -0,0 +1,9 @@ +import { redirect } from "react-router"; + +export const clientLoader = () => { + throw redirect("/"); +}; + +export default function SignIn() { + return null; +} diff --git a/apps/web/app/routes/redirects/core/signin.tsx b/apps/web/app/routes/redirects/core/signin.tsx new file mode 100644 index 000000000..e440e8399 --- /dev/null +++ b/apps/web/app/routes/redirects/core/signin.tsx @@ -0,0 +1,9 @@ +import { redirect } from "react-router"; + +export const clientLoader = () => { + throw redirect("/"); +}; + +export default function Signin() { + return null; +} diff --git a/apps/web/app/routes/redirects/extended/index.ts b/apps/web/app/routes/redirects/extended/index.ts new file mode 100644 index 000000000..7f2c496e1 --- /dev/null +++ b/apps/web/app/routes/redirects/extended/index.ts @@ -0,0 +1,3 @@ +import type { RouteConfigEntry } from "@react-router/dev/routes"; + +export const extendedRedirectRoutes: RouteConfigEntry[] = []; diff --git a/apps/web/app/routes/redirects/index.ts b/apps/web/app/routes/redirects/index.ts new file mode 100644 index 000000000..4c78ea354 --- /dev/null +++ b/apps/web/app/routes/redirects/index.ts @@ -0,0 +1,10 @@ +import type { RouteConfigEntry } from "@react-router/dev/routes"; +import { coreRedirectRoutes } from "./core"; +import { extendedRedirectRoutes } from "./extended"; + +/** + * REDIRECT ROUTES + * Centralized configuration for all route redirects + * Migrated from Next.js next.config.js redirects + */ +export const redirectRoutes: RouteConfigEntry[] = [...coreRedirectRoutes, ...extendedRedirectRoutes]; diff --git a/apps/web/app/types/next-image.d.ts b/apps/web/app/types/next-image.d.ts new file mode 100644 index 000000000..d3f520b7e --- /dev/null +++ b/apps/web/app/types/next-image.d.ts @@ -0,0 +1,12 @@ +declare module "next/image" { + type Props = React.ComponentProps<"img"> & { + src: string; + fill?: boolean; + priority?: boolean; + quality?: number; + placeholder?: "blur" | "empty"; + blurDataURL?: string; + }; + const Image: React.FC; + export default Image; +} diff --git a/apps/web/app/types/next-link.d.ts b/apps/web/app/types/next-link.d.ts new file mode 100644 index 000000000..c724e3aec --- /dev/null +++ b/apps/web/app/types/next-link.d.ts @@ -0,0 +1,12 @@ +declare module "next/link" { + type Props = React.ComponentProps<"a"> & { + href: string; + replace?: boolean; + prefetch?: boolean; + scroll?: boolean; + shallow?: boolean; + }; + + const Link: React.FC; + export default Link; +} diff --git a/apps/web/app/types/next-navigation.d.ts b/apps/web/app/types/next-navigation.d.ts new file mode 100644 index 000000000..67a80c4fa --- /dev/null +++ b/apps/web/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/web/app/types/next-script.d.ts b/apps/web/app/types/next-script.d.ts new file mode 100644 index 000000000..299b5ed4c --- /dev/null +++ b/apps/web/app/types/next-script.d.ts @@ -0,0 +1,15 @@ +declare module "next/script" { + type ScriptProps = { + src?: string; + id?: string; + strategy?: "beforeInteractive" | "afterInteractive" | "lazyOnload" | "worker"; + onLoad?: () => void; + onError?: () => void; + children?: string; + defer?: boolean; + [key: string]: any; + }; + + const Script: React.FC; + export default Script; +} diff --git a/apps/web/app/types/react-router-virtual.d.ts b/apps/web/app/types/react-router-virtual.d.ts new file mode 100644 index 000000000..abf3b638e --- /dev/null +++ b/apps/web/app/types/react-router-virtual.d.ts @@ -0,0 +1,5 @@ +declare module "virtual:react-router/server-build" { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const build: any; + export default build; +} diff --git a/apps/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx b/apps/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx index 1ce060b01..0f9c03b39 100644 --- a/apps/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx +++ b/apps/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx @@ -10,6 +10,13 @@ import { useTranslation } from "@plane/i18n"; import { getButtonStyling } from "@plane/propel/button"; import { ContentWrapper } from "@plane/ui"; import { cn } from "@plane/utils"; +// assets +import ctaL1Dark from "@/app/assets/workspace-active-cycles/cta-l-1-dark.webp?url"; +import ctaL1Light from "@/app/assets/workspace-active-cycles/cta-l-1-light.webp?url"; +import ctaR1Dark from "@/app/assets/workspace-active-cycles/cta-r-1-dark.webp?url"; +import ctaR1Light from "@/app/assets/workspace-active-cycles/cta-r-1-light.webp?url"; +import ctaR2Dark from "@/app/assets/workspace-active-cycles/cta-r-2-dark.webp?url"; +import ctaR2Light from "@/app/assets/workspace-active-cycles/cta-r-2-light.webp?url"; // components import { ProIcon } from "@/components/common/pro-icon"; // hooks @@ -93,7 +100,7 @@ export const WorkspaceActiveCyclesUpgrade = observer(() => {
{
- r-1 + r-1 - r-2 + r-2
diff --git a/apps/web/ce/components/automations/list/wrapper.tsx b/apps/web/ce/components/automations/list/wrapper.tsx new file mode 100644 index 000000000..1db9b9da6 --- /dev/null +++ b/apps/web/ce/components/automations/list/wrapper.tsx @@ -0,0 +1,7 @@ +type Props = { + projectId: string; + workspaceSlug: string; + children: React.ReactNode; +}; + +export const AutomationsListWrapper: React.FC = (props) => <>{props.children}; diff --git a/apps/web/ce/components/cycles/active-cycle/root.tsx b/apps/web/ce/components/cycles/active-cycle/root.tsx index 8ac331988..b0933218e 100644 --- a/apps/web/ce/components/cycles/active-cycle/root.tsx +++ b/apps/web/ce/components/cycles/active-cycle/root.tsx @@ -2,10 +2,14 @@ import { useMemo } from "react"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; import { Disclosure } from "@headlessui/react"; // plane imports import { useTranslation } from "@plane/i18n"; import { Row } from "@plane/ui"; +// assets +import darkActiveCycleAsset from "@/app/assets/empty-state/cycle/active-dark.webp?url"; +import lightActiveCycleAsset from "@/app/assets/empty-state/cycle/active-light.webp?url"; // components import { ActiveCycleStats } from "@/components/cycles/active-cycle/cycle-stats"; import { ActiveCycleProductivity } from "@/components/cycles/active-cycle/productivity"; @@ -16,7 +20,6 @@ import { CyclesListItem } from "@/components/cycles/list/cycles-list-item"; import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root"; // hooks import { useCycle } from "@/hooks/store/use-cycle"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import type { ActiveCycleIssueDetails } from "@/store/issue/cycle"; interface IActiveCycleDetails { @@ -28,13 +31,15 @@ interface IActiveCycleDetails { export const ActiveCycleRoot: React.FC = observer((props) => { const { workspaceSlug, projectId, cycleId: propsCycleId, showHeader = true } = props; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // store hooks const { currentProjectActiveCycleId } = useCycle(); // derived values const cycleId = propsCycleId ?? currentProjectActiveCycleId; - const activeCycleResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/cycle/active" }); + const activeCycleResolvedPath = resolvedTheme === "light" ? lightActiveCycleAsset : darkActiveCycleAsset; // fetch cycle details const { handleFiltersUpdate, diff --git a/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/assets.tsx b/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/assets.tsx index c0bd72cca..d9f7f8785 100644 --- a/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/assets.tsx +++ b/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/assets.tsx @@ -1,12 +1,16 @@ import Image from "next/image"; +import { useTheme } from "next-themes"; // plane imports import { useTranslation } from "@plane/i18n"; -// hooks -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +// assets +import darkAssetsAsset from "@/app/assets/empty-state/wiki/navigation-pane/assets-dark.webp?url"; +import lightAssetsAsset from "@/app/assets/empty-state/wiki/navigation-pane/assets-light.webp?url"; export const PageNavigationPaneAssetsTabEmptyState = () => { + // theme hook + const { resolvedTheme } = useTheme(); // asset resolved path - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/wiki/navigation-pane/assets" }); + const resolvedPath = resolvedTheme === "light" ? lightAssetsAsset : darkAssetsAsset; // translation const { t } = useTranslation(); diff --git a/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/outline.tsx b/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/outline.tsx index 1e692ea3b..9b6744cc9 100644 --- a/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/outline.tsx +++ b/apps/web/ce/components/pages/navigation-pane/tab-panels/empty-states/outline.tsx @@ -1,12 +1,16 @@ import Image from "next/image"; +import { useTheme } from "next-themes"; // plane imports import { useTranslation } from "@plane/i18n"; -// hooks -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +// assets +import darkOutlineAsset from "@/app/assets/empty-state/wiki/navigation-pane/outline-dark.webp?url"; +import lightOutlineAsset from "@/app/assets/empty-state/wiki/navigation-pane/outline-light.webp?url"; export const PageNavigationPaneOutlineTabEmptyState = () => { + // theme hook + const { resolvedTheme } = useTheme(); // asset resolved path - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/wiki/navigation-pane/outline" }); + const resolvedPath = resolvedTheme === "light" ? lightOutlineAsset : darkOutlineAsset; // translation const { t } = useTranslation(); diff --git a/apps/web/ce/layouts/project-wrapper.tsx b/apps/web/ce/layouts/project-wrapper.tsx index 71dba5044..d2e2b1a99 100644 --- a/apps/web/ce/layouts/project-wrapper.tsx +++ b/apps/web/ce/layouts/project-wrapper.tsx @@ -5,7 +5,7 @@ import { ProjectAuthWrapper as CoreProjectAuthWrapper } from "@/layouts/auth-lay export type IProjectAuthWrapper = { workspaceSlug: string; - projectId: string; + projectId?: string; children: React.ReactNode; }; diff --git a/apps/web/core/components/account/auth-forms/auth-root.tsx b/apps/web/core/components/account/auth-forms/auth-root.tsx index 9d01a2d3c..173914647 100644 --- a/apps/web/core/components/account/auth-forms/auth-root.tsx +++ b/apps/web/core/components/account/auth-forms/auth-root.tsx @@ -8,10 +8,10 @@ import { useTheme } from "next-themes"; import { API_BASE_URL } from "@plane/constants"; import { OAuthOptions } from "@plane/ui"; // 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 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 { diff --git a/apps/web/core/components/analytics/empty-state.tsx b/apps/web/core/components/analytics/empty-state.tsx index 3704f3e84..5cc27ba3a 100644 --- a/apps/web/core/components/analytics/empty-state.tsx +++ b/apps/web/core/components/analytics/empty-state.tsx @@ -1,8 +1,11 @@ import React from "react"; import Image from "next/image"; +import { useTheme } from "next-themes"; // plane package imports import { cn } from "@plane/utils"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; +// assets +import darkBackgroundAsset from "@/app/assets/empty-state/analytics/empty-grid-background-dark.webp?url"; +import lightBackgroundAsset from "@/app/assets/empty-state/analytics/empty-grid-background-light.webp?url"; type Props = { title: string; @@ -12,7 +15,9 @@ type Props = { }; const AnalyticsEmptyState = ({ title, description, assetPath, className }: Props) => { - const backgroundReolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-grid-background" }); + // theme hook + const { resolvedTheme } = useTheme(); + const backgroundReolvedPath = resolvedTheme === "light" ? lightBackgroundAsset : darkBackgroundAsset; return (
+const RadarChart = lazy(() => import("@plane/propel/charts/radar-chart").then((mod) => ({ default: mod.RadarChart, })) @@ -63,29 +63,31 @@ const ProjectInsights = observer(() => { ) : (
{projectInsightsData && ( - }> + + ]} + margin={{ top: 0, right: 40, bottom: 10, left: 40 }} + showTooltip + angleAxis={{ + key: "name", + }} + /> + )}
{t("workspace_analytics.summary_of_projects")}
diff --git a/apps/web/core/components/api-token/empty-state.tsx b/apps/web/core/components/api-token/empty-state.tsx index 194966df5..fafdef359 100644 --- a/apps/web/core/components/api-token/empty-state.tsx +++ b/apps/web/core/components/api-token/empty-state.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; // ui import { Button } from "@plane/propel/button"; // assets -import emptyApiTokens from "@/public/empty-state/api-token.svg"; +import emptyApiTokens from "@/app/assets/empty-state/api-token.svg?url"; type Props = { onClick: () => void; diff --git a/apps/web/core/components/auth-screens/not-authorized-view.tsx b/apps/web/core/components/auth-screens/not-authorized-view.tsx index 58265a41f..3156d4a97 100644 --- a/apps/web/core/components/auth-screens/not-authorized-view.tsx +++ b/apps/web/core/components/auth-screens/not-authorized-view.tsx @@ -1,12 +1,12 @@ import React from "react"; import { observer } from "mobx-react"; import Image from "next/image"; +// assets +import ProjectNotAuthorizedImg from "@/app/assets/auth/project-not-authorized.svg?url"; +import Unauthorized from "@/app/assets/auth/unauthorized.svg?url"; +import WorkspaceNotAuthorizedImg from "@/app/assets/auth/workspace-not-authorized.svg?url"; // layouts import DefaultLayout from "@/layouts/default-layout"; -// images -import ProjectNotAuthorizedImg from "@/public/auth/project-not-authorized.svg"; -import Unauthorized from "@/public/auth/unauthorized.svg"; -import WorkspaceNotAuthorizedImg from "@/public/auth/workspace-not-authorized.svg"; type Props = { actionButton?: React.ReactNode; diff --git a/apps/web/core/components/auth-screens/project/join-project.tsx b/apps/web/core/components/auth-screens/project/join-project.tsx index 39fbaba35..1e10e2ec8 100644 --- a/apps/web/core/components/auth-screens/project/join-project.tsx +++ b/apps/web/core/components/auth-screens/project/join-project.tsx @@ -5,11 +5,11 @@ import { useParams } from "next/navigation"; import { ClipboardList } from "lucide-react"; // plane imports import { Button } from "@plane/propel/button"; +// assets +import Unauthorized from "@/app/assets/auth/unauthorized.svg?url"; // hooks import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; -// assets -import Unauthorized from "@/public/auth/unauthorized.svg"; type Props = { projectId?: string; diff --git a/apps/web/core/components/common/latest-feature-block.tsx b/apps/web/core/components/common/latest-feature-block.tsx index 327b03ea5..81b74cb61 100644 --- a/apps/web/core/components/common/latest-feature-block.tsx +++ b/apps/web/core/components/common/latest-feature-block.tsx @@ -4,7 +4,7 @@ import { useTheme } from "next-themes"; // icons import { Lightbulb } from "lucide-react"; // images -import latestFeatures from "@/public/onboarding/onboarding-pages.webp"; +import latestFeatures from "@/app/assets/onboarding/onboarding-pages.webp?url"; export const LatestFeatureBlock = () => { const { resolvedTheme } = useTheme(); diff --git a/apps/web/core/components/common/logo-spinner.tsx b/apps/web/core/components/common/logo-spinner.tsx index 0e902f307..e92e30e0a 100644 --- a/apps/web/core/components/common/logo-spinner.tsx +++ b/apps/web/core/components/common/logo-spinner.tsx @@ -1,8 +1,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/web/core/components/core/modals/bulk-delete-issues-modal.tsx b/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx index f24638297..3a9420105 100644 --- a/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx +++ b/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +import { useTheme } from "next-themes"; import type { SubmitHandler } from "react-hook-form"; import { useForm } from "react-hook-form"; import { Search } from "lucide-react"; @@ -14,13 +15,17 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { ISearchIssueResponse, IUser } from "@plane/types"; import { EIssuesStoreType } from "@plane/types"; import { Loader } from "@plane/ui"; +// assets +import darkIssuesAsset from "@/app/assets/empty-state/search/issues-dark.webp?url"; +import lightIssuesAsset from "@/app/assets/empty-state/search/issues-light.webp?url"; +import darkSearchAsset from "@/app/assets/empty-state/search/search-dark.webp?url"; +import lightSearchAsset from "@/app/assets/empty-state/search/search-light.webp?url"; // components import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; // hooks import { useIssues } from "@/hooks/store/use-issues"; import useDebounce from "@/hooks/use-debounce"; // services -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { ProjectService } from "@/services/project"; // local components import { BulkDeleteIssuesModalItem } from "./bulk-delete-issues-modal-item"; @@ -45,6 +50,8 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { const [query, setQuery] = useState(""); const [issues, setIssues] = useState([]); const [isSearching, setIsSearching] = useState(false); + // theme hook + const { resolvedTheme } = useTheme(); // hooks const { issues: { removeBulkIssues }, @@ -52,8 +59,8 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { const { t } = useTranslation(); // derived values const debouncedSearchTerm: string = useDebounce(query, 500); - const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" }); - const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" }); + const searchResolvedPath = resolvedTheme === "light" ? lightSearchAsset : darkSearchAsset; + const issuesResolvedPath = resolvedTheme === "light" ? lightIssuesAsset : darkIssuesAsset; useEffect(() => { if (!isOpen || !workspaceSlug || !projectId) return; diff --git a/apps/web/core/components/core/modals/issue-search-modal-empty-state.tsx b/apps/web/core/components/core/modals/issue-search-modal-empty-state.tsx index 1d5428d09..ede002b53 100644 --- a/apps/web/core/components/core/modals/issue-search-modal-empty-state.tsx +++ b/apps/web/core/components/core/modals/issue-search-modal-empty-state.tsx @@ -1,10 +1,15 @@ import React from "react"; +import { useTheme } from "next-themes"; // plane imports import { useTranslation } from "@plane/i18n"; import type { ISearchIssueResponse } from "@plane/types"; +// assets +import darkIssuesAsset from "@/app/assets/empty-state/search/issues-dark.webp?url"; +import lightIssuesAsset from "@/app/assets/empty-state/search/issues-light.webp?url"; +import darkSearchAsset from "@/app/assets/empty-state/search/search-dark.webp?url"; +import lightSearchAsset from "@/app/assets/empty-state/search/search-light.webp?url"; // components import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; interface EmptyStateProps { issues: ISearchIssueResponse[]; @@ -19,11 +24,13 @@ export const IssueSearchModalEmptyState: React.FC = ({ debouncedSearchTerm, isSearching, }) => { + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // derived values - const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" }); - const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" }); + const searchResolvedPath = resolvedTheme === "light" ? lightSearchAsset : darkSearchAsset; + const issuesResolvedPath = resolvedTheme === "light" ? lightIssuesAsset : darkIssuesAsset; const EmptyStateContainer = ({ children }: { children: React.ReactNode }) => (
{children}
diff --git a/apps/web/core/components/core/sidebar/progress-stats/assignee.tsx b/apps/web/core/components/core/sidebar/progress-stats/assignee.tsx index dbb4dcebb..c3607eafc 100644 --- a/apps/web/core/components/core/sidebar/progress-stats/assignee.tsx +++ b/apps/web/core/components/core/sidebar/progress-stats/assignee.tsx @@ -4,10 +4,11 @@ import Image from "next/image"; import { useTranslation } from "@plane/i18n"; import { Avatar } from "@plane/ui"; import { getFileURL } from "@plane/utils"; +// assets +import emptyMembers from "@/app/assets/empty-state/empty_members.svg?url"; +import userImage from "@/app/assets/user.png?url"; // components import { SingleProgressStats } from "@/components/core/sidebar/single-progress-stats"; -// public -import emptyMembers from "@/public/empty-state/empty_members.svg"; export type TAssigneeData = { id: string | undefined; @@ -56,7 +57,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) => title={
- User + User
{t("no_assignee")}
diff --git a/apps/web/core/components/core/sidebar/progress-stats/label.tsx b/apps/web/core/components/core/sidebar/progress-stats/label.tsx index 3897ec13e..df510a73f 100644 --- a/apps/web/core/components/core/sidebar/progress-stats/label.tsx +++ b/apps/web/core/components/core/sidebar/progress-stats/label.tsx @@ -2,10 +2,10 @@ import { observer } from "mobx-react"; import Image from "next/image"; // plane imports import { useTranslation } from "@plane/i18n"; +// assets +import emptyLabel from "@/app/assets/empty-state/empty_label.svg?url"; // components import { SingleProgressStats } from "@/components/core/sidebar/single-progress-stats"; -// public -import emptyLabel from "@/public/empty-state/empty_label.svg"; export type TLabelData = { id: string | undefined; diff --git a/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx b/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx index 8bdd0ad6c..4f6ffe725 100644 --- a/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx +++ b/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx @@ -4,6 +4,7 @@ import type { FC } from "react"; import { Fragment, useCallback, useRef, useState } from "react"; import { isEmpty } from "lodash-es"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; import { CalendarCheck } from "lucide-react"; // headless ui import { Tab } from "@headlessui/react"; @@ -17,18 +18,24 @@ import { EIssuesStoreType } from "@plane/types"; // ui import { Loader, Avatar } from "@plane/ui"; import { cn, renderFormattedDate, renderFormattedDateWithoutYear, getFileURL } from "@plane/utils"; +// assets +import darkAssigneeAsset from "@/app/assets/empty-state/active-cycle/assignee-dark.webp?url"; +import lightAssigneeAsset from "@/app/assets/empty-state/active-cycle/assignee-light.webp?url"; +import darkLabelAsset from "@/app/assets/empty-state/active-cycle/label-dark.webp?url"; +import lightLabelAsset from "@/app/assets/empty-state/active-cycle/label-light.webp?url"; +import darkPriorityAsset from "@/app/assets/empty-state/active-cycle/priority-dark.webp?url"; +import lightPriorityAsset from "@/app/assets/empty-state/active-cycle/priority-light.webp?url"; +import userImage from "@/app/assets/user.png?url"; // components import { SingleProgressStats } from "@/components/core/sidebar/single-progress-stats"; import { StateDropdown } from "@/components/dropdowns/state/dropdown"; import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; -// helpers // hooks import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useIssues } from "@/hooks/store/use-issues"; import { useIntersectionObserver } from "@/hooks/use-intersection-observer"; import useLocalStorage from "@/hooks/use-local-storage"; // plane web components -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/issue-identifier"; // store import type { ActiveCycleIssueDetails } from "@/store/issue/cycle"; @@ -50,12 +57,14 @@ export const ActiveCycleStats: FC = observer((props) => { const issuesContainerRef = useRef(null); // states const [issuesLoaderElement, setIssueLoaderElement] = useState(null); + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // derived values - const priorityResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/priority" }); - const assigneesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/assignee" }); - const labelsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/label" }); + const priorityResolvedPath = resolvedTheme === "light" ? lightPriorityAsset : darkPriorityAsset; + const assigneesResolvedPath = resolvedTheme === "light" ? lightAssigneeAsset : darkAssigneeAsset; + const labelsResolvedPath = resolvedTheme === "light" ? lightLabelAsset : darkLabelAsset; const currentValue = (tab: string | null) => { switch (tab) { @@ -294,7 +303,7 @@ export const ActiveCycleStats: FC = observer((props) => { title={
- User + User
{t("no_assignee")}
diff --git a/apps/web/core/components/cycles/active-cycle/productivity.tsx b/apps/web/core/components/cycles/active-cycle/productivity.tsx index d7677cbf8..5663ff9e6 100644 --- a/apps/web/core/components/cycles/active-cycle/productivity.tsx +++ b/apps/web/core/components/cycles/active-cycle/productivity.tsx @@ -2,17 +2,19 @@ import type { FC } from "react"; import { Fragment } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; +import { useTheme } from "next-themes"; // plane imports import { useTranslation } from "@plane/i18n"; import type { ICycle, TCycleEstimateType } from "@plane/types"; import { Loader } from "@plane/ui"; +// assets +import darkChartAsset from "@/app/assets/empty-state/active-cycle/chart-dark.webp?url"; +import lightChartAsset from "@/app/assets/empty-state/active-cycle/chart-light.webp?url"; // components import ProgressChart from "@/components/core/sidebar/progress-chart"; import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; -// constants +// hooks import { useCycle } from "@/hooks/store/use-cycle"; -// plane web constants -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { EstimateTypeDropdown } from "../dropdowns/estimate-type-dropdown"; export type ActiveCycleProductivityProps = { @@ -23,13 +25,15 @@ export type ActiveCycleProductivityProps = { export const ActiveCycleProductivity: FC = observer((props) => { const { workspaceSlug, projectId, cycle } = props; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // hooks const { getEstimateTypeByCycleId, setEstimateType } = useCycle(); // derived values const estimateType: TCycleEstimateType = (cycle && getEstimateTypeByCycleId(cycle.id)) || "issues"; - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/chart" }); + const resolvedPath = resolvedTheme === "light" ? lightChartAsset : darkChartAsset; const onChange = async (value: TCycleEstimateType) => { if (!workspaceSlug || !projectId || !cycle || !cycle.id) return; diff --git a/apps/web/core/components/cycles/active-cycle/progress.tsx b/apps/web/core/components/cycles/active-cycle/progress.tsx index d2e53c09f..1e2812d60 100644 --- a/apps/web/core/components/cycles/active-cycle/progress.tsx +++ b/apps/web/core/components/cycles/active-cycle/progress.tsx @@ -2,16 +2,18 @@ import type { FC } from "react"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; // plane imports import { PROGRESS_STATE_GROUPS_DETAILS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import type { TWorkItemFilterCondition } from "@plane/shared-state"; import type { ICycle } from "@plane/types"; import { LinearProgressIndicator, Loader } from "@plane/ui"; +// assets +import darkProgressAsset from "@/app/assets/empty-state/active-cycle/progress-dark.webp?url"; +import lightProgressAsset from "@/app/assets/empty-state/active-cycle/progress-light.webp?url"; // components import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; -// hooks -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; export type ActiveCycleProgressProps = { cycle: ICycle | null; @@ -22,6 +24,8 @@ export type ActiveCycleProgressProps = { export const ActiveCycleProgress: FC = observer((props) => { const { handleFiltersUpdate, cycle } = props; + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // derived values @@ -39,7 +43,7 @@ export const ActiveCycleProgress: FC = observer((props backlog: cycle?.backlog_issues, } : {}; - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/progress" }); + const resolvedPath = resolvedTheme === "light" ? lightProgressAsset : darkProgressAsset; return cycle && cycle.hasOwnProperty("started_issues") ? (
diff --git a/apps/web/core/components/cycles/archived-cycles/view.tsx b/apps/web/core/components/cycles/archived-cycles/view.tsx index e5a2fd0db..9491dc0f6 100644 --- a/apps/web/core/components/cycles/archived-cycles/view.tsx +++ b/apps/web/core/components/cycles/archived-cycles/view.tsx @@ -1,6 +1,9 @@ import type { FC } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; +// assets +import AllFiltersImage from "@/app/assets/empty-state/cycle/all-filters.svg?url"; +import NameFilterImage from "@/app/assets/empty-state/cycle/name-filter.svg?url"; // components import { CyclesList } from "@/components/cycles/list"; // ui @@ -8,9 +11,6 @@ import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module // hooks import { useCycle } from "@/hooks/store/use-cycle"; import { useCycleFilter } from "@/hooks/store/use-cycle-filter"; -// assets -import AllFiltersImage from "@/public/empty-state/cycle/all-filters.svg"; -import NameFilterImage from "@/public/empty-state/cycle/name-filter.svg"; export interface IArchivedCyclesView { workspaceSlug: string; diff --git a/apps/web/core/components/cycles/cycles-view.tsx b/apps/web/core/components/cycles/cycles-view.tsx index 2f9216454..f0958d213 100644 --- a/apps/web/core/components/cycles/cycles-view.tsx +++ b/apps/web/core/components/cycles/cycles-view.tsx @@ -3,15 +3,15 @@ import { observer } from "mobx-react"; import Image from "next/image"; // components import { useTranslation } from "@plane/i18n"; +// assets +import AllFiltersImage from "@/app/assets/empty-state/cycle/all-filters.svg?url"; +import NameFilterImage from "@/app/assets/empty-state/cycle/name-filter.svg?url"; +// components import { CyclesList } from "@/components/cycles/list"; -// ui import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader"; // hooks import { useCycle } from "@/hooks/store/use-cycle"; import { useCycleFilter } from "@/hooks/store/use-cycle-filter"; -// assets -import AllFiltersImage from "@/public/empty-state/cycle/all-filters.svg"; -import NameFilterImage from "@/public/empty-state/cycle/name-filter.svg"; export interface ICyclesView { workspaceSlug: string; diff --git a/apps/web/core/components/editor/pdf/document.tsx b/apps/web/core/components/editor/pdf/document.tsx index 88b0b08ef..6887c3f63 100644 --- a/apps/web/core/components/editor/pdf/document.tsx +++ b/apps/web/core/components/editor/pdf/document.tsx @@ -3,30 +3,40 @@ import type { PageProps } from "@react-pdf/renderer"; import { Document, Font, Page } from "@react-pdf/renderer"; import { Html } from "react-pdf-html"; +// assets +import interBold from "@/app/assets/fonts/inter/bold.ttf?url"; +import interHeavy from "@/app/assets/fonts/inter/heavy.ttf?url"; +import interLight from "@/app/assets/fonts/inter/light.ttf?url"; +import interMedium from "@/app/assets/fonts/inter/medium.ttf?url"; +import interRegular from "@/app/assets/fonts/inter/regular.ttf?url"; +import interSemibold from "@/app/assets/fonts/inter/semibold.ttf?url"; +import interThin from "@/app/assets/fonts/inter/thin.ttf?url"; +import interUltraBold from "@/app/assets/fonts/inter/ultrabold.ttf?url"; +import interUltraLight from "@/app/assets/fonts/inter/ultralight.ttf?url"; // constants import { EDITOR_PDF_DOCUMENT_STYLESHEET } from "@/constants/editor"; Font.register({ family: "Inter", fonts: [ - { src: "/fonts/inter/thin.ttf", fontWeight: "thin" }, - { src: "/fonts/inter/thin.ttf", fontWeight: "thin", fontStyle: "italic" }, - { src: "/fonts/inter/ultralight.ttf", fontWeight: "ultralight" }, - { src: "/fonts/inter/ultralight.ttf", fontWeight: "ultralight", fontStyle: "italic" }, - { src: "/fonts/inter/light.ttf", fontWeight: "light" }, - { src: "/fonts/inter/light.ttf", fontWeight: "light", fontStyle: "italic" }, - { src: "/fonts/inter/regular.ttf", fontWeight: "normal" }, - { src: "/fonts/inter/regular.ttf", fontWeight: "normal", fontStyle: "italic" }, - { src: "/fonts/inter/medium.ttf", fontWeight: "medium" }, - { src: "/fonts/inter/medium.ttf", fontWeight: "medium", fontStyle: "italic" }, - { src: "/fonts/inter/semibold.ttf", fontWeight: "semibold" }, - { src: "/fonts/inter/semibold.ttf", fontWeight: "semibold", fontStyle: "italic" }, - { src: "/fonts/inter/bold.ttf", fontWeight: "bold" }, - { src: "/fonts/inter/bold.ttf", fontWeight: "bold", fontStyle: "italic" }, - { src: "/fonts/inter/extrabold.ttf", fontWeight: "ultrabold" }, - { src: "/fonts/inter/extrabold.ttf", fontWeight: "ultrabold", fontStyle: "italic" }, - { src: "/fonts/inter/heavy.ttf", fontWeight: "heavy" }, - { src: "/fonts/inter/heavy.ttf", fontWeight: "heavy", fontStyle: "italic" }, + { src: interThin, fontWeight: "thin" }, + { src: interThin, fontWeight: "thin", fontStyle: "italic" }, + { src: interUltraLight, fontWeight: "ultralight" }, + { src: interUltraLight, fontWeight: "ultralight", fontStyle: "italic" }, + { src: interLight, fontWeight: "light" }, + { src: interLight, fontWeight: "light", fontStyle: "italic" }, + { src: interRegular, fontWeight: "normal" }, + { src: interRegular, fontWeight: "normal", fontStyle: "italic" }, + { src: interMedium, fontWeight: "medium" }, + { src: interMedium, fontWeight: "medium", fontStyle: "italic" }, + { src: interSemibold, fontWeight: "semibold" }, + { src: interSemibold, fontWeight: "semibold", fontStyle: "italic" }, + { src: interBold, fontWeight: "bold" }, + { src: interBold, fontWeight: "bold", fontStyle: "italic" }, + { src: interUltraBold, fontWeight: "ultrabold" }, + { src: interUltraBold, fontWeight: "ultrabold", fontStyle: "italic" }, + { src: interHeavy, fontWeight: "heavy" }, + { src: interHeavy, fontWeight: "heavy", fontStyle: "italic" }, ], }); diff --git a/apps/web/core/components/empty-state/detailed-empty-state-root.tsx b/apps/web/core/components/empty-state/detailed-empty-state-root.tsx index 1776e72ae..69252650f 100644 --- a/apps/web/core/components/empty-state/detailed-empty-state-root.tsx +++ b/apps/web/core/components/empty-state/detailed-empty-state-root.tsx @@ -2,7 +2,6 @@ import React from "react"; import { observer } from "mobx-react"; -import Image from "next/image"; // ui import { Button } from "@plane/propel/button"; // utils @@ -85,9 +84,7 @@ export const DetailedEmptyState: React.FC = observer((props) => { {description &&

{description}

}
- {assetPath && ( - {title} - )} + {assetPath && {title}} {hasButtons && (
diff --git a/apps/web/core/components/home/home-dashboard-widgets.tsx b/apps/web/core/components/home/home-dashboard-widgets.tsx index e1fa834d2..362c64d11 100644 --- a/apps/web/core/components/home/home-dashboard-widgets.tsx +++ b/apps/web/core/components/home/home-dashboard-widgets.tsx @@ -1,15 +1,18 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; +import { useTheme } from "next-themes"; // plane imports import { useTranslation } from "@plane/i18n"; import type { THomeWidgetKeys, THomeWidgetProps } from "@plane/types"; +// assets +import darkWidgetsAsset from "@/app/assets/empty-state/dashboard/widgets-dark.webp?url"; +import lightWidgetsAsset from "@/app/assets/empty-state/dashboard/widgets-light.webp?url"; // components import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; // hooks import { useHome } from "@/hooks/store/use-home"; import { useProject } from "@/hooks/store/use-project"; // plane web components -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { HomePageHeader } from "@/plane-web/components/home/header"; // local imports import { StickiesWidget } from "../stickies/widget"; @@ -56,6 +59,8 @@ export const DashboardWidgets = observer(() => { const { workspaceSlug } = useParams(); // navigation const pathname = usePathname(); + // theme hook + const { resolvedTheme } = useTheme(); // store hooks const { toggleWidgetSettings, widgetsMap, showWidgetSettings, orderedWidgets, isAnyWidgetEnabled, loading } = useHome(); @@ -63,7 +68,7 @@ export const DashboardWidgets = observer(() => { // plane hooks const { t } = useTranslation(); // derived values - const noWidgetsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/dashboard/widgets" }); + const noWidgetsResolvedPath = resolvedTheme === "light" ? lightWidgetsAsset : darkWidgetsAsset; // derived values const isWikiApp = pathname.includes(`/${workspaceSlug.toString()}/pages`); diff --git a/apps/web/core/components/home/widgets/links/create-update-link-modal.tsx b/apps/web/core/components/home/widgets/links/create-update-link-modal.tsx index 5039402cf..bdebc8c93 100644 --- a/apps/web/core/components/home/widgets/links/create-update-link-modal.tsx +++ b/apps/web/core/components/home/widgets/links/create-update-link-modal.tsx @@ -9,7 +9,6 @@ import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/propel/button"; import type { TLinkEditableFields } from "@plane/types"; -import { TLink } from "@plane/types"; import { Input, ModalCore } from "@plane/ui"; import type { TLinkOperations } from "./use-links"; diff --git a/apps/web/core/components/icons/attachment/audio-file-icon.tsx b/apps/web/core/components/icons/attachment/audio-file-icon.tsx index 27860e29f..6eae85ef9 100644 --- a/apps/web/core/components/icons/attachment/audio-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/audio-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import AudioFileIcon from "@/public/attachment/audio-icon.png"; +import AudioFileIcon from "@/app/assets/attachment/audio-icon.png?url"; export type AudioIconProps = { width?: number; diff --git a/apps/web/core/components/icons/attachment/css-file-icon.tsx b/apps/web/core/components/icons/attachment/css-file-icon.tsx index 4d794df62..7a9b0333d 100644 --- a/apps/web/core/components/icons/attachment/css-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/css-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import CssFileIcon from "@/public/attachment/css-icon.png"; +import CssFileIcon from "@/app/assets/attachment/css-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/csv-file-icon.tsx b/apps/web/core/components/icons/attachment/csv-file-icon.tsx index 8391da029..234578771 100644 --- a/apps/web/core/components/icons/attachment/csv-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/csv-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import CSVFileIcon from "@/public/attachment/csv-icon.png"; +import CSVFileIcon from "@/app/assets/attachment/csv-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/default-file-icon.tsx b/apps/web/core/components/icons/attachment/default-file-icon.tsx index 3f7b58e45..2954a1a0e 100644 --- a/apps/web/core/components/icons/attachment/default-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/default-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import DefaultFileIcon from "@/public/attachment/default-icon.png"; +import DefaultFileIcon from "@/app/assets/attachment/default-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/doc-file-icon.tsx b/apps/web/core/components/icons/attachment/doc-file-icon.tsx index a3e84ec9d..126878225 100644 --- a/apps/web/core/components/icons/attachment/doc-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/doc-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import DocFileIcon from "@/public/attachment/doc-icon.png"; +import DocFileIcon from "@/app/assets/attachment/doc-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/figma-file-icon.tsx b/apps/web/core/components/icons/attachment/figma-file-icon.tsx index 939f9d522..a80c5c8e7 100644 --- a/apps/web/core/components/icons/attachment/figma-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/figma-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import FigmaFileIcon from "@/public/attachment/figma-icon.png"; +import FigmaFileIcon from "@/app/assets/attachment/figma-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/html-file-icon.tsx b/apps/web/core/components/icons/attachment/html-file-icon.tsx index 69da2d30e..65bbef6a7 100644 --- a/apps/web/core/components/icons/attachment/html-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/html-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import HtmlFileIcon from "@/public/attachment/html-icon.png"; +import HtmlFileIcon from "@/app/assets/attachment/html-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/img-file-icon.tsx b/apps/web/core/components/icons/attachment/img-file-icon.tsx index 3e7695f93..67a7f4e76 100644 --- a/apps/web/core/components/icons/attachment/img-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/img-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import ImgFileIcon from "@/public/attachment/img-icon.png"; +import ImgFileIcon from "@/app/assets/attachment/img-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/jpg-file-icon.tsx b/apps/web/core/components/icons/attachment/jpg-file-icon.tsx index 49be740c9..25f29947c 100644 --- a/apps/web/core/components/icons/attachment/jpg-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/jpg-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import JpgFileIcon from "@/public/attachment/jpg-icon.png"; +import JpgFileIcon from "@/app/assets/attachment/jpg-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/js-file-icon.tsx b/apps/web/core/components/icons/attachment/js-file-icon.tsx index 3fb4dd489..b324498da 100644 --- a/apps/web/core/components/icons/attachment/js-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/js-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import JsFileIcon from "@/public/attachment/js-icon.png"; +import JsFileIcon from "@/app/assets/attachment/js-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/pdf-file-icon.tsx b/apps/web/core/components/icons/attachment/pdf-file-icon.tsx index 0ac9bdd87..f1685fbc2 100644 --- a/apps/web/core/components/icons/attachment/pdf-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/pdf-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import PDFFileIcon from "@/public/attachment/pdf-icon.png"; +import PDFFileIcon from "@/app/assets/attachment/pdf-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/png-file-icon.tsx b/apps/web/core/components/icons/attachment/png-file-icon.tsx index 5b136cdc2..693396799 100644 --- a/apps/web/core/components/icons/attachment/png-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/png-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import PngFileIcon from "@/public/attachment/png-icon.png"; +import PngFileIcon from "@/app/assets/attachment/png-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/rar-file-icon.tsx b/apps/web/core/components/icons/attachment/rar-file-icon.tsx index 0c3dd88e1..753fe6129 100644 --- a/apps/web/core/components/icons/attachment/rar-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/rar-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import RarFileIcon from "@/public/attachment/rar-icon.png"; +import RarFileIcon from "@/app/assets/attachment/rar-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/sheet-file-icon.tsx b/apps/web/core/components/icons/attachment/sheet-file-icon.tsx index 586469ee1..16a35cc9c 100644 --- a/apps/web/core/components/icons/attachment/sheet-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/sheet-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import SheetFileIcon from "@/public/attachment/excel-icon.png"; +import SheetFileIcon from "@/app/assets/attachment/excel-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/svg-file-icon.tsx b/apps/web/core/components/icons/attachment/svg-file-icon.tsx index aaea9a164..be3ca74c6 100644 --- a/apps/web/core/components/icons/attachment/svg-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/svg-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import SvgFileIcon from "@/public/attachment/svg-icon.png"; +import SvgFileIcon from "@/app/assets/attachment/svg-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/txt-file-icon.tsx b/apps/web/core/components/icons/attachment/txt-file-icon.tsx index 1fa5e98ae..643f3fb75 100644 --- a/apps/web/core/components/icons/attachment/txt-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/txt-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import TxtFileIcon from "@/public/attachment/txt-icon.png"; +import TxtFileIcon from "@/app/assets/attachment/txt-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/video-file-icon.tsx b/apps/web/core/components/icons/attachment/video-file-icon.tsx index 5b57551d5..398d90121 100644 --- a/apps/web/core/components/icons/attachment/video-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/video-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import VideoFileIcon from "@/public/attachment/video-icon.png"; +import VideoFileIcon from "@/app/assets/attachment/video-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/icons/attachment/zip-file-icon.tsx b/apps/web/core/components/icons/attachment/zip-file-icon.tsx index 8f5d0388b..ac978eaa6 100644 --- a/apps/web/core/components/icons/attachment/zip-file-icon.tsx +++ b/apps/web/core/components/icons/attachment/zip-file-icon.tsx @@ -1,7 +1,7 @@ import React from "react"; import Image from "next/image"; // image -import ZipFileIcon from "@/public/attachment/zip-icon.png"; +import ZipFileIcon from "@/app/assets/attachment/zip-icon.png?url"; // type import type { ImageIconPros } from "../types"; diff --git a/apps/web/core/components/inbox/modals/select-duplicate.tsx b/apps/web/core/components/inbox/modals/select-duplicate.tsx index 3d89eb68c..a44b7e48f 100644 --- a/apps/web/core/components/inbox/modals/select-duplicate.tsx +++ b/apps/web/core/components/inbox/modals/select-duplicate.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import { useParams } from "next/navigation"; +import { useTheme } from "next-themes"; import { Search } from "lucide-react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; // plane imports @@ -9,12 +10,16 @@ import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { ISearchIssueResponse } from "@plane/types"; import { Loader } from "@plane/ui"; +// assets +import darkIssuesAsset from "@/app/assets/empty-state/search/issues-dark.webp?url"; +import lightIssuesAsset from "@/app/assets/empty-state/search/issues-light.webp?url"; +import darkSearchAsset from "@/app/assets/empty-state/search/search-dark.webp?url"; +import lightSearchAsset from "@/app/assets/empty-state/search/search-light.webp?url"; // components import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; // hooks import { useProject } from "@/hooks/store/use-project"; import useDebounce from "@/hooks/use-debounce"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; // services import { ProjectService } from "@/services/project"; @@ -35,13 +40,15 @@ export const SelectDuplicateInboxIssueModal: React.FC = (props) => { const [query, setQuery] = useState(""); const [issues, setIssues] = useState([]); const [isSearching, setIsSearching] = useState(false); + // theme hook + const { resolvedTheme } = useTheme(); // hooks const { getProjectById } = useProject(); const { t } = useTranslation(); // derived values const debouncedSearchTerm: string = useDebounce(query, 500); - const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" }); - const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" }); + const searchResolvedPath = resolvedTheme === "light" ? lightSearchAsset : darkSearchAsset; + const issuesResolvedPath = resolvedTheme === "light" ? lightIssuesAsset : darkIssuesAsset; useEffect(() => { if (!isOpen || !workspaceSlug || !projectId) return; diff --git a/apps/web/core/components/inbox/root.tsx b/apps/web/core/components/inbox/root.tsx index ec2411a47..fec25cede 100644 --- a/apps/web/core/components/inbox/root.tsx +++ b/apps/web/core/components/inbox/root.tsx @@ -14,7 +14,6 @@ import { InboxSidebar } from "@/components/inbox/sidebar"; import { InboxLayoutLoader } from "@/components/ui/loader/layouts/project-inbox/inbox-layout-loader"; // hooks import { useProjectInbox } from "@/hooks/store/use-project-inbox"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; type TInboxIssueRoot = { workspaceSlug: string; @@ -32,8 +31,6 @@ export const InboxIssueRoot: FC = observer((props) => { const { t } = useTranslation(); // hooks const { loader, error, currentTab, handleCurrentTab, fetchInboxIssues } = useProjectInbox(); - // derived values - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" }); useEffect(() => { if (!inboxAccessible || !workspaceSlug || !projectId) return; diff --git a/apps/web/core/components/instance/maintenance-view.tsx b/apps/web/core/components/instance/maintenance-view.tsx index 87f243497..4c7675233 100644 --- a/apps/web/core/components/instance/maintenance-view.tsx +++ b/apps/web/core/components/instance/maintenance-view.tsx @@ -3,13 +3,13 @@ import type { FC } from "react"; import Image from "next/image"; import { useTheme } from "next-themes"; +// assets +import maintenanceModeDarkModeImage from "@/app/assets/instance/maintenance-mode-dark.svg?url"; +import maintenanceModeLightModeImage from "@/app/assets/instance/maintenance-mode-light.svg?url"; // layouts import DefaultLayout from "@/layouts/default-layout"; // components import { MaintenanceMessage } from "@/plane-web/components/instance"; -// images -import maintenanceModeDarkModeImage from "@/public/instance/maintenance-mode-dark.svg"; -import maintenanceModeLightModeImage from "@/public/instance/maintenance-mode-light.svg"; export const MaintenanceView: FC = () => { // hooks diff --git a/apps/web/core/components/instance/not-ready-view.tsx b/apps/web/core/components/instance/not-ready-view.tsx index b44a8b3e0..41460e26c 100644 --- a/apps/web/core/components/instance/not-ready-view.tsx +++ b/apps/web/core/components/instance/not-ready-view.tsx @@ -7,12 +7,10 @@ import { useTheme } from "next-themes"; import { GOD_MODE_URL } from "@plane/constants"; import { Button } from "@plane/propel/button"; import { PlaneLockup } from "@plane/propel/icons"; -// helpers -// images // assets -import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg"; -import PlaneBackgroundPattern from "@/public/auth/background-pattern.svg"; -import PlaneTakeOffImage from "@/public/plane-takeoff.png"; +import PlaneBackgroundPatternDark from "@/app/assets/auth/background-pattern-dark.svg?url"; +import PlaneBackgroundPattern from "@/app/assets/auth/background-pattern.svg?url"; +import PlaneTakeOffImage from "@/app/assets/plane-takeoff.png?url"; export const InstanceNotReady: FC = () => { const { resolvedTheme } = useTheme(); diff --git a/apps/web/core/components/integration/github/root.tsx b/apps/web/core/components/integration/github/root.tsx index cccef5da4..b041c69fd 100644 --- a/apps/web/core/components/integration/github/root.tsx +++ b/apps/web/core/components/integration/github/root.tsx @@ -11,7 +11,8 @@ import { MembersPropertyIcon } from "@plane/propel/icons"; // types import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { IGithubRepoCollaborator, IGithubServiceImportFormData } from "@plane/types"; -// ui +// assets +import GithubLogo from "@/app/assets/services/github.png?url"; // components import { GithubImportConfigure, @@ -24,8 +25,6 @@ import { import { APP_INTEGRATIONS, IMPORTER_SERVICES_LIST, WORKSPACE_INTEGRATIONS } from "@/constants/fetch-keys"; // hooks import { useAppRouter } from "@/hooks/use-app-router"; -// images -import GithubLogo from "@/public/services/github.png"; // services import { IntegrationService, GithubIntegrationService } from "@/services/integrations"; diff --git a/apps/web/core/components/integration/guide.tsx b/apps/web/core/components/integration/guide.tsx index d5ef4625b..8fd4c35ed 100644 --- a/apps/web/core/components/integration/guide.tsx +++ b/apps/web/core/components/integration/guide.tsx @@ -14,7 +14,9 @@ import { useTranslation } from "@plane/i18n"; // types import { Button } from "@plane/propel/button"; import type { IImporterService } from "@plane/types"; -// ui +// assets +import GithubLogo from "@/app/assets/services/github.png?url"; +import JiraLogo from "@/app/assets/services/jira.svg?url"; // components import { DeleteImportModal, GithubImporterRoot, JiraImporterRoot, SingleImport } from "@/components/integration"; import { ImportExportSettingsLoader } from "@/components/ui/loader/settings/import-and-export"; @@ -22,9 +24,6 @@ import { ImportExportSettingsLoader } from "@/components/ui/loader/settings/impo import { IMPORTER_SERVICES_LIST } from "@/constants/fetch-keys"; // hooks import { useUser } from "@/hooks/store/user"; -// assets -import GithubLogo from "@/public/services/github.png"; -import JiraLogo from "@/public/services/jira.svg"; // services import { IntegrationService } from "@/services/integrations"; diff --git a/apps/web/core/components/integration/jira/root.tsx b/apps/web/core/components/integration/jira/root.tsx index f2d5c9955..e32be4493 100644 --- a/apps/web/core/components/integration/jira/root.tsx +++ b/apps/web/core/components/integration/jira/root.tsx @@ -12,13 +12,12 @@ import { Button } from "@plane/propel/button"; import { MembersPropertyIcon } from "@plane/propel/icons"; // types import type { IJiraImporterForm } from "@plane/types"; -// ui +// assets +import JiraLogo from "@/app/assets/services/jira.svg?url"; // fetch keys import { IMPORTER_SERVICES_LIST } from "@/constants/fetch-keys"; // hooks import { useAppRouter } from "@/hooks/use-app-router"; -// assets -import JiraLogo from "@/public/services/jira.svg"; // services import { JiraImporterService } from "@/services/integrations"; // components diff --git a/apps/web/core/components/integration/single-integration-card.tsx b/apps/web/core/components/integration/single-integration-card.tsx index b6169d83d..686b775fe 100644 --- a/apps/web/core/components/integration/single-integration-card.tsx +++ b/apps/web/core/components/integration/single-integration-card.tsx @@ -13,6 +13,9 @@ import { Tooltip } from "@plane/propel/tooltip"; import type { IAppIntegration, IWorkspaceIntegration } from "@plane/types"; // ui import { Loader } from "@plane/ui"; +// assets +import GithubLogo from "@/app/assets/services/github.png?url"; +import SlackLogo from "@/app/assets/services/slack.png?url"; // constants import { WORKSPACE_INTEGRATIONS } from "@/constants/fetch-keys"; // hooks @@ -21,9 +24,6 @@ import { useUserPermissions } from "@/hooks/store/user"; import useIntegrationPopup from "@/hooks/use-integration-popup"; import { usePlatformOS } from "@/hooks/use-platform-os"; // services -// icons -import GithubLogo from "@/public/services/github.png"; -import SlackLogo from "@/public/services/slack.png"; import { IntegrationService } from "@/services/integrations"; type Props = { diff --git a/apps/web/core/components/issues/issue-detail/root.tsx b/apps/web/core/components/issues/issue-detail/root.tsx index 10c77beb5..7ed13f584 100644 --- a/apps/web/core/components/issues/issue-detail/root.tsx +++ b/apps/web/core/components/issues/issue-detail/root.tsx @@ -9,6 +9,8 @@ import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/propel/toast"; import type { TIssue } from "@plane/types"; import { EIssuesStoreType } from "@plane/types"; +// assets +import emptyIssue from "@/app/assets/empty-state/issue.svg?url"; // components import { EmptyState } from "@/components/common/empty-state"; // hooks @@ -18,8 +20,6 @@ import { useIssueDetail } from "@/hooks/store/use-issue-detail"; import { useIssues } from "@/hooks/store/use-issues"; import { useUserPermissions } from "@/hooks/store/user"; import { useAppRouter } from "@/hooks/use-app-router"; -// images -import emptyIssue from "@/public/empty-state/issue.svg"; // local components import { IssuePeekOverview } from "../peek-overview"; import { IssueMainContent } from "./main-content"; diff --git a/apps/web/core/components/issues/issue-layouts/empty-states/project-view.tsx b/apps/web/core/components/issues/issue-layouts/empty-states/project-view.tsx index 165263177..e89b31f9e 100644 --- a/apps/web/core/components/issues/issue-layouts/empty-states/project-view.tsx +++ b/apps/web/core/components/issues/issue-layouts/empty-states/project-view.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { EUserPermissions, EUserPermissionsLevel, WORK_ITEM_TRACKER_ELEMENTS } from "@plane/constants"; import { EmptyStateDetailed } from "@plane/propel/empty-state"; import { EIssuesStoreType } from "@plane/types"; +// components import { captureClick } from "@/helpers/event-tracker.helper"; // hooks import { useCommandPalette } from "@/hooks/store/use-command-palette"; diff --git a/apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index 6c3daaaf7..10702ea09 100644 --- a/apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/apps/web/core/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -7,6 +7,8 @@ import { GLOBAL_VIEW_TRACKER_ELEMENTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@pl import { EmptyStateDetailed } from "@plane/propel/empty-state"; import type { EIssueLayoutTypes } from "@plane/types"; import { EIssuesStoreType, STATIC_VIEW_TYPES } from "@plane/types"; +// assets +import emptyView from "@/app/assets/empty-state/view.svg?url"; // components import { IssuePeekOverview } from "@/components/issues/peek-overview"; import { WorkspaceActiveLayout } from "@/components/views/helper"; diff --git a/apps/web/core/components/issues/peek-overview/error.tsx b/apps/web/core/components/issues/peek-overview/error.tsx index 8c87912cf..59f901a86 100644 --- a/apps/web/core/components/issues/peek-overview/error.tsx +++ b/apps/web/core/components/issues/peek-overview/error.tsx @@ -3,12 +3,12 @@ import type { FC } from "react"; import { MoveRight } from "lucide-react"; import { Tooltip } from "@plane/propel/tooltip"; +// assets +import emptyIssue from "@/app/assets/empty-state/issue.svg?url"; // components import { EmptyState } from "@/components/common/empty-state"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; -// images -import emptyIssue from "@/public/empty-state/issue.svg"; type TIssuePeekOverviewError = { removeRoutePeekId: () => void; diff --git a/apps/web/core/components/issues/workspace-draft/root.tsx b/apps/web/core/components/issues/workspace-draft/root.tsx index 636b898e3..e35d9ec3f 100644 --- a/apps/web/core/components/issues/workspace-draft/root.tsx +++ b/apps/web/core/components/issues/workspace-draft/root.tsx @@ -12,15 +12,11 @@ import { EUserWorkspaceRoles } from "@plane/types"; // components import { cn } from "@plane/utils"; import { captureClick } from "@/helpers/event-tracker.helper"; -// constants - -// helpers // hooks import { useCommandPalette } from "@/hooks/store/use-command-palette"; import { useProject } from "@/hooks/store/use-project"; import { useUserPermissions } from "@/hooks/store/user"; import { useWorkspaceDraftIssues } from "@/hooks/store/workspace-draft"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties"; // components import { DraftIssueBlock } from "./draft-issue-block"; @@ -45,7 +41,6 @@ export const WorkspaceDraftIssuesRoot: FC = observer( [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER], EUserPermissionsLevel.WORKSPACE ); - const noProjectResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/draft/draft-issues-empty" }); //swr hook for fetching issue properties useWorkspaceIssueProperties(workspaceSlug); diff --git a/apps/web/core/components/modules/archived-modules/view.tsx b/apps/web/core/components/modules/archived-modules/view.tsx index bc1d82640..ba2498e29 100644 --- a/apps/web/core/components/modules/archived-modules/view.tsx +++ b/apps/web/core/components/modules/archived-modules/view.tsx @@ -1,6 +1,9 @@ import type { FC } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; +// assets +import AllFiltersImage from "@/app/assets/empty-state/module/all-filters.svg?url"; +import NameFilterImage from "@/app/assets/empty-state/module/name-filter.svg?url"; // components import { ModuleListItem, ModulePeekOverview } from "@/components/modules"; // ui @@ -8,9 +11,6 @@ import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module // hooks import { useModule } from "@/hooks/store/use-module"; import { useModuleFilter } from "@/hooks/store/use-module-filter"; -// assets -import AllFiltersImage from "@/public/empty-state/module/all-filters.svg"; -import NameFilterImage from "@/public/empty-state/module/name-filter.svg"; export interface IArchivedModulesView { workspaceSlug: string; diff --git a/apps/web/core/components/modules/modules-list-view.tsx b/apps/web/core/components/modules/modules-list-view.tsx index 039faec59..75f83f281 100644 --- a/apps/web/core/components/modules/modules-list-view.tsx +++ b/apps/web/core/components/modules/modules-list-view.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "@plane/i18n"; import { EmptyStateDetailed } from "@plane/propel/empty-state"; import { EUserProjectRoles } from "@plane/types"; import { ContentWrapper, Row, ERowVariant } from "@plane/ui"; +// components import { ListLayout } from "@/components/core/list"; import { ModuleCardItem, ModuleListItem, ModulePeekOverview, ModulesListGanttChartView } from "@/components/modules"; import { CycleModuleBoardLayoutLoader } from "@/components/ui/loader/cycle-module-board-loader"; diff --git a/apps/web/core/components/onboarding/tour/root.tsx b/apps/web/core/components/onboarding/tour/root.tsx index 39cdd254a..ffbc33e08 100644 --- a/apps/web/core/components/onboarding/tour/root.tsx +++ b/apps/web/core/components/onboarding/tour/root.tsx @@ -2,23 +2,22 @@ import { useState } from "react"; import { observer } from "mobx-react"; -import type { StaticImageData } from "next/image"; import Image from "next/image"; // plane imports import { PRODUCT_TOUR_TRACKER_ELEMENTS } from "@plane/constants"; import { Button } from "@plane/propel/button"; import { CloseIcon, PlaneLockup } from "@plane/propel/icons"; +// assets +import CyclesTour from "@/app/assets/onboarding/cycles.webp?url"; +import IssuesTour from "@/app/assets/onboarding/issues.webp?url"; +import ModulesTour from "@/app/assets/onboarding/modules.webp?url"; +import PagesTour from "@/app/assets/onboarding/pages.webp?url"; +import ViewsTour from "@/app/assets/onboarding/views.webp?url"; // helpers import { captureClick } from "@/helpers/event-tracker.helper"; // hooks import { useCommandPalette } from "@/hooks/store/use-command-palette"; import { useUser } from "@/hooks/store/user"; -// assets -import CyclesTour from "@/public/onboarding/cycles.webp"; -import IssuesTour from "@/public/onboarding/issues.webp"; -import ModulesTour from "@/public/onboarding/modules.webp"; -import PagesTour from "@/public/onboarding/pages.webp"; -import ViewsTour from "@/public/onboarding/views.webp"; // local imports import { TourSidebar } from "./sidebar"; @@ -32,7 +31,7 @@ const TOUR_STEPS: { key: TTourSteps; title: string; description: string; - image: StaticImageData; + image: any; prevStep?: TTourSteps; nextStep?: TTourSteps; }[] = [ diff --git a/apps/web/core/components/power-k/ui/pages/open-entity/workspace-settings-menu.tsx b/apps/web/core/components/power-k/ui/pages/open-entity/workspace-settings-menu.tsx index 666336e5a..2a2cbdb0d 100644 --- a/apps/web/core/components/power-k/ui/pages/open-entity/workspace-settings-menu.tsx +++ b/apps/web/core/components/power-k/ui/pages/open-entity/workspace-settings-menu.tsx @@ -1,6 +1,5 @@ "use client"; -import { WORKSPACE_SETTINGS_ICONS } from "app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar"; import { observer } from "mobx-react"; // plane types import { EUserPermissionsLevel, WORKSPACE_SETTINGS } from "@plane/constants"; @@ -11,6 +10,7 @@ import { PowerKSettingsMenu } from "@/components/power-k/menus/settings"; // hooks import { useUserPermissions } from "@/hooks/store/user"; import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper"; +import { WORKSPACE_SETTINGS_ICONS } from "app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/sidebar"; type Props = { context: TPowerKContext; diff --git a/apps/web/core/components/profile/overview/activity.tsx b/apps/web/core/components/profile/overview/activity.tsx index 2af5e2079..daed93b91 100644 --- a/apps/web/core/components/profile/overview/activity.tsx +++ b/apps/web/core/components/profile/overview/activity.tsx @@ -8,6 +8,8 @@ import { useTranslation } from "@plane/i18n"; import { EmptyStateCompact } from "@plane/propel/empty-state"; import { Loader, Card } from "@plane/ui"; import { calculateTimeAgo, getFileURL } from "@plane/utils"; +// assets +import recentActivityEmptyState from "@/app/assets/empty-state/recent_activity.svg?url"; // components import { ActivityMessage, IssueLink } from "@/components/core/activity"; import { ProfileEmptyState } from "@/components/ui/profile-empty-state"; @@ -16,8 +18,6 @@ import { USER_PROFILE_ACTIVITY } from "@/constants/fetch-keys"; // helpers // hooks import { useUser } from "@/hooks/store/user"; -// assets -import recentActivityEmptyState from "@/public/empty-state/recent_activity.svg"; // services import { UserService } from "@/services/user.service"; diff --git a/apps/web/core/components/project/integration-card.tsx b/apps/web/core/components/project/integration-card.tsx index 87e3519a5..1c32eb835 100644 --- a/apps/web/core/components/project/integration-card.tsx +++ b/apps/web/core/components/project/integration-card.tsx @@ -6,15 +6,15 @@ import { useParams } from "next/navigation"; import useSWR, { mutate } from "swr"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import type { IWorkspaceIntegration } from "@plane/types"; +// assets +import GithubLogo from "@/app/assets/logos/github-square.png?url"; +import SlackLogo from "@/app/assets/services/slack.png?url"; // components import { SelectRepository, SelectChannel } from "@/components/integration"; // constants import { PROJECT_GITHUB_REPOSITORY } from "@/constants/fetch-keys"; -// icons -import GithubLogo from "@/public/logos/github-square.png"; -import SlackLogo from "@/public/services/slack.png"; +// services import { ProjectService } from "@/services/project"; -// types type Props = { integration: IWorkspaceIntegration; diff --git a/apps/web/core/components/project/multi-select-modal.tsx b/apps/web/core/components/project/multi-select-modal.tsx index add390304..fa64c1e90 100644 --- a/apps/web/core/components/project/multi-select-modal.tsx +++ b/apps/web/core/components/project/multi-select-modal.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { xor } from "lodash-es"; import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; import { Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; // plane ui @@ -8,14 +9,15 @@ import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/propel/button"; import { CloseIcon } from "@plane/propel/icons"; import { Checkbox, EModalPosition, EModalWidth, ModalCore } from "@plane/ui"; -// components import { cn } from "@plane/utils"; +// assets +import darkProjectAsset from "@/app/assets/empty-state/search/project-dark.webp?url"; +import lightProjectAsset from "@/app/assets/empty-state/search/project-light.webp?url"; +// components import { Logo } from "@/components/common/logo"; import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root"; -// helpers // hooks import { useProject } from "@/hooks/store/use-project"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; type Props = { isOpen: boolean; @@ -33,6 +35,8 @@ export const ProjectMultiSelectModal: React.FC = observer((props) => { const [isSubmitting, setIsSubmitting] = useState(false); // refs const moveButtonRef = useRef(null); + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // store hooks @@ -48,9 +52,7 @@ export const ProjectMultiSelectModal: React.FC = observer((props) => { const projectQuery = `${project?.identifier} ${project?.name}`.toLowerCase(); return projectQuery.includes(searchTerm.toLowerCase()); }); - const filteredProjectResolvedPath = useResolvedAssetPath({ - basePath: "/empty-state/search/project", - }); + const filteredProjectResolvedPath = resolvedTheme === "light" ? lightProjectAsset : darkProjectAsset; useEffect(() => { if (isOpen) setSelectedProjectIds(selectedProjectIdsProp); diff --git a/apps/web/core/components/project/root.tsx b/apps/web/core/components/project/root.tsx index e917ecc4a..02e9a8e4a 100644 --- a/apps/web/core/components/project/root.tsx +++ b/apps/web/core/components/project/root.tsx @@ -71,9 +71,7 @@ export const ProjectRoot = observer(() => { }, [clearAllFilters, clearAllAppliedDisplayFilters, workspaceSlug]); useEffect(() => { - isArchived - ? updateDisplayFilters(workspaceSlug.toString(), { archived_projects: true }) - : updateDisplayFilters(workspaceSlug.toString(), { archived_projects: false }); + updateDisplayFilters(workspaceSlug.toString(), { archived_projects: isArchived }); }, [pathname]); return ( diff --git a/apps/web/core/components/project/settings/helper.tsx b/apps/web/core/components/project/settings/helper.tsx index 0b7b18432..23a0db53b 100644 --- a/apps/web/core/components/project/settings/helper.tsx +++ b/apps/web/core/components/project/settings/helper.tsx @@ -3,6 +3,7 @@ import { ChevronRight } from "lucide-react"; import { PROJECT_TRACKER_ELEMENTS } from "@plane/constants"; import { EPillVariant, Pill, EPillSize } from "@plane/propel/pill"; import { ToggleSwitch } from "@plane/ui"; +import { joinUrlPath } from "@plane/utils"; import type { TProperties } from "@/plane-web/constants/project/settings/features"; type Props = { @@ -17,7 +18,7 @@ type Props = { export const ProjectFeatureToggle = (props: Props) => { const { workspaceSlug, projectId, featureItem, value, handleSubmit, disabled } = props; return featureItem.href ? ( - +
{ const { workspaceSlug, intersectionElement, columnCount } = props; // navigation const pathname = usePathname(); + // theme hook + const { resolvedTheme } = useTheme(); // plane hooks const { t } = useTranslation(); // store hooks @@ -55,10 +62,8 @@ export const StickiesList = observer((props: TProps) => { [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER, EUserWorkspaceRoles.GUEST], EUserPermissionsLevel.WORKSPACE ); - const stickiesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/stickies/stickies" }); - const stickiesSearchResolvedPath = useResolvedAssetPath({ - basePath: "/empty-state/stickies/stickies-search", - }); + const stickiesResolvedPath = resolvedTheme === "light" ? lightStickiesAsset : darkStickiesAsset; + const stickiesSearchResolvedPath = resolvedTheme === "light" ? lightStickiesSearchAsset : darkStickiesSearchAsset; const masonryRef = useRef(null); const handleLayout = () => { diff --git a/apps/web/core/components/stickies/sticky/inputs.tsx b/apps/web/core/components/stickies/sticky/inputs.tsx index dad7b4761..068a9aa4f 100644 --- a/apps/web/core/components/stickies/sticky/inputs.tsx +++ b/apps/web/core/components/stickies/sticky/inputs.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useRef } from "react"; -// import dynamic from "next/dynamic"; import { usePathname } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; // plane imports diff --git a/apps/web/core/components/web-hooks/empty-state.tsx b/apps/web/core/components/web-hooks/empty-state.tsx index 88ab56001..2fca6476d 100644 --- a/apps/web/core/components/web-hooks/empty-state.tsx +++ b/apps/web/core/components/web-hooks/empty-state.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; // ui import { Button } from "@plane/propel/button"; // assets -import EmptyWebhook from "@/public/empty-state/web-hook.svg"; +import EmptyWebhook from "@/app/assets/empty-state/web-hook.svg?url"; type Props = { onClick: () => void; diff --git a/apps/web/core/components/workspace-notifications/root.tsx b/apps/web/core/components/workspace-notifications/root.tsx index 406f8beae..307001dc3 100644 --- a/apps/web/core/components/workspace-notifications/root.tsx +++ b/apps/web/core/components/workspace-notifications/root.tsx @@ -14,7 +14,6 @@ import { LogoSpinner } from "@/components/common/logo-spinner"; import { useWorkspaceNotifications } from "@/hooks/store/notifications"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUserPermissions } from "@/hooks/store/user"; -import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties"; // plane web imports import { useNotificationPreview } from "@/plane-web/hooks/use-notification-preview"; @@ -42,7 +41,6 @@ export const NotificationsRoot = observer(({ workspaceSlug }: NotificationsRootP // derived values const { workspace_slug, project_id, issue_id, is_inbox_issue } = notificationLiteByNotificationId(currentSelectedNotificationId); - const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" }); // fetching workspace work item properties useWorkspaceIssueProperties(workspaceSlug); diff --git a/apps/web/core/hooks/use-app-router.tsx b/apps/web/core/hooks/use-app-router.tsx index 71841cbe9..9fb11e2c4 100644 --- a/apps/web/core/hooks/use-app-router.tsx +++ b/apps/web/core/hooks/use-app-router.tsx @@ -1,4 +1,4 @@ // router from n-progress-bar -import { useRouter } from "@/lib/b-progress"; +import { useRouter } from "next/navigation"; export const useAppRouter = () => useRouter(); diff --git a/apps/web/core/hooks/use-resolved-asset-path.tsx b/apps/web/core/hooks/use-resolved-asset-path.tsx deleted file mode 100644 index 4233dc646..000000000 --- a/apps/web/core/hooks/use-resolved-asset-path.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useTheme } from "next-themes"; - -type AssetPathConfig = { - basePath: string; - additionalPath?: string; - extension?: string; - includeThemeInPath?: boolean; -}; - -export const useResolvedAssetPath = ({ - basePath, - additionalPath = "", - extension = "webp", - includeThemeInPath = true, -}: AssetPathConfig) => { - // hooks - const { resolvedTheme } = useTheme(); - // resolved theme - const theme = resolvedTheme === "light" ? "light" : "dark"; - - if (!includeThemeInPath) { - return `${additionalPath && additionalPath !== "" ? `${basePath}${additionalPath}` : basePath}.${extension}`; - } - - return `${additionalPath && additionalPath !== "" ? `${basePath}${additionalPath}` : basePath}-${theme}.${extension}`; -}; diff --git a/apps/web/core/layouts/auth-layout/project-wrapper.tsx b/apps/web/core/layouts/auth-layout/project-wrapper.tsx index a341f202d..cc4803946 100644 --- a/apps/web/core/layouts/auth-layout/project-wrapper.tsx +++ b/apps/web/core/layouts/auth-layout/project-wrapper.tsx @@ -32,7 +32,7 @@ import { persistence } from "@/local-db/storage.sqlite"; interface IProjectAuthWrapper { workspaceSlug: string; - projectId: string; + projectId?: string; children: ReactNode; isLoading?: boolean; } diff --git a/apps/web/core/layouts/auth-layout/workspace-wrapper.tsx b/apps/web/core/layouts/auth-layout/workspace-wrapper.tsx index 779018db0..5b48a6992 100644 --- a/apps/web/core/layouts/auth-layout/workspace-wrapper.tsx +++ b/apps/web/core/layouts/auth-layout/workspace-wrapper.tsx @@ -17,6 +17,11 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { Tooltip } from "@plane/propel/tooltip"; // components import { cn } from "@plane/utils"; +// assets +import PlaneBlackLogo from "@/app/assets/plane-logos/black-horizontal-with-blue-logo.png?url"; +import PlaneWhiteLogo from "@/app/assets/plane-logos/white-horizontal-with-blue-logo.png?url"; +import WorkSpaceNotAvailable from "@/app/assets/workspace/workspace-not-available.png?url"; +// components import { LogoSpinner } from "@/components/common/logo-spinner"; // hooks import { useFavorite } from "@/hooks/store/use-favorite"; @@ -28,10 +33,6 @@ import { useUser, useUserPermissions } from "@/hooks/store/user"; import { usePlatformOS } from "@/hooks/use-platform-os"; // local import { persistence } from "@/local-db/storage.sqlite"; -// images -import PlaneBlackLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; -import PlaneWhiteLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; -import WorkSpaceNotAvailable from "@/public/workspace/workspace-not-available.png"; interface IWorkspaceAuthWrapper { children: ReactNode; diff --git a/apps/web/core/lib/b-progress/AppProgressBar.tsx b/apps/web/core/lib/b-progress/AppProgressBar.tsx index 0856200bd..1bd9362c0 100644 --- a/apps/web/core/lib/b-progress/AppProgressBar.tsx +++ b/apps/web/core/lib/b-progress/AppProgressBar.tsx @@ -1,6 +1,140 @@ -import { useRouter as useBProgressRouter } from "@bprogress/next"; +"use client"; -export function useRouter() { - const router = useBProgressRouter(); - return router; +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; + /** Whether to disable the progress bar */ + isDisabled?: boolean; +} + +/** + * Configuration for the progress bar + */ +const PROGRESS_CONFIG: Readonly = { + showSpinner: false, + minimum: 0.1, + speed: 400, + trickleSpeed: 800, + easing: "ease", + trickle: true, + delay: 0, + isDisabled: import.meta.env.PROD, // Disable progress bar in production builds +} 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. + * + * Note: Progress bar is disabled in production builds. + * + * @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(() => { + // Skip initialization in production builds + if (PROGRESS_CONFIG.isDisabled) { + return; + } + + // 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(() => { + // Skip navigation tracking in production builds + if (PROGRESS_CONFIG.isDisabled) { + return; + } + + 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/web/core/lib/posthog-provider.tsx b/apps/web/core/lib/posthog-provider.tsx index 7be998692..b9b02a1fe 100644 --- a/apps/web/core/lib/posthog-provider.tsx +++ b/apps/web/core/lib/posthog-provider.tsx @@ -1,9 +1,8 @@ "use client"; import type { FC, ReactNode } from "react"; -import { useEffect } from "react"; +import { lazy, Suspense, useEffect } from "react"; import { observer } from "mobx-react"; -import dynamic from "next/dynamic"; import { useParams } from "next/navigation"; import posthog from "posthog-js"; import { PostHogProvider as PHProvider } from "posthog-js/react"; @@ -17,7 +16,7 @@ import { useInstance } from "@/hooks/store/use-instance"; import { useWorkspace } from "@/hooks/store/use-workspace"; import { useUser, useUserPermissions } from "@/hooks/store/user"; // dynamic imports -const PostHogPageView = dynamic(() => import("@/lib/posthog-view"), { ssr: false }); +const PostHogPageView = lazy(() => import("@/lib/posthog-view")); export interface IPosthogWrapper { children: ReactNode; @@ -99,7 +98,9 @@ const PostHogProvider: FC = observer((props) => { if (is_posthog_enabled) return ( - + + + {children} ); diff --git a/apps/web/core/store/user/base-permissions.store.ts b/apps/web/core/store/user/base-permissions.store.ts index b20049dc7..817574b93 100644 --- a/apps/web/core/store/user/base-permissions.store.ts +++ b/apps/web/core/store/user/base-permissions.store.ts @@ -32,7 +32,10 @@ export interface IBaseUserPermissionStore { workspaceInfoBySlug: (workspaceSlug: string) => IWorkspaceMemberMe | undefined; getWorkspaceRoleByWorkspaceSlug: (workspaceSlug: string) => TUserPermissions | EUserWorkspaceRoles | undefined; getProjectRolesByWorkspaceSlug: (workspaceSlug: string) => IUserProjectsRole; - getProjectRoleByWorkspaceSlugAndProjectId: (workspaceSlug: string, projectId: string) => EUserPermissions | undefined; + getProjectRoleByWorkspaceSlugAndProjectId: ( + workspaceSlug: string, + projectId?: string + ) => EUserPermissions | undefined; allowPermissions: ( allowPermissions: ETempUserRole[], level: TUserPermissionsLevel, @@ -142,7 +145,7 @@ export abstract class BaseUserPermissionStore implements IBaseUserPermissionStor */ abstract getProjectRoleByWorkspaceSlugAndProjectId: ( workspaceSlug: string, - projectId: string + projectId?: string ) => EUserPermissions | undefined; /** diff --git a/apps/web/middleware.js b/apps/web/middleware.js new file mode 100644 index 000000000..d1e43c9ff --- /dev/null +++ b/apps/web/middleware.js @@ -0,0 +1,14 @@ +import { next } from "@vercel/edge"; + +export default function middleware() { + return next({ + headers: { + "Referrer-Policy": "origin-when-cross-origin", + "X-Frame-Options": "DENY", + "X-Content-Type-Options": "nosniff", + "X-DNS-Prefetch-Control": "on", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + }, + }); +} + diff --git a/apps/web/nginx/nginx.conf b/apps/web/nginx/nginx.conf new file mode 100644 index 000000000..160fcb9be --- /dev/null +++ b/apps/web/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/ /index.html; + } + } +} + diff --git a/apps/web/package.json b/apps/web/package.json index 81e60e2b2..b090c4fea 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -3,23 +3,25 @@ "version": "1.1.0", "private": true, "license": "AGPL-3.0", + "type": "module", "scripts": { - "dev": "next dev --port 3000", - "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=3000 node server.mjs", + "build": "react-router build", + "preview": "react-router build && cross-env NODE_ENV=production PORT=3000 node server.mjs", + "start": "serve -s build/client -l 3000", + "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 821", - "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": { - "@atlaskit/pragmatic-drag-and-drop": "catalog:", "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "catalog:", "@atlaskit/pragmatic-drag-and-drop-hitbox": "catalog:", - "@bprogress/next": "^3.2.12", - "@headlessui/react": "^1.7.3", + "@atlaskit/pragmatic-drag-and-drop": "catalog:", + "@bprogress/core": "catalog:", + "@headlessui/react": "^1.7.19", "@intercom/messenger-js-sdk": "^0.0.12", "@plane/constants": "workspace:*", "@plane/editor": "workspace:*", @@ -33,34 +35,45 @@ "@plane/utils": "workspace:*", "@popperjs/core": "^2.11.8", "@react-pdf/renderer": "^3.4.5", + "@react-router/express": "^7.9.3", + "@react-router/node": "^7.9.3", "@tanstack/react-table": "^8.21.3", "axios": "catalog:", "clsx": "^2.0.0", "cmdk": "^1.0.0", "comlink": "^4.4.1", + "compression": "^1.8.1", + "cross-env": "^7.0.3", "date-fns": "^4.1.0", - "dotenv": "^16.0.3", + "dotenv": "^16.4.5", "emoji-picker-react": "^4.5.16", "export-to-csv": "^1.4.0", + "express": "^5.1.0", + "http-proxy-middleware": "^3.0.5", + "isbot": "^5.1.31", "lodash-es": "catalog:", "lucide-react": "catalog:", - "mobx": "catalog:", "mobx-react": "catalog:", "mobx-utils": "catalog:", - "next": "catalog:", + "mobx": "catalog:", + "morgan": "^1.10.1", "next-themes": "^0.2.1", "posthog-js": "^1.131.3", - "react": "catalog:", "react-color": "^2.19.3", "react-dom": "catalog:", "react-dropzone": "^14.2.3", + "react-fast-compare": "^3.2.2", "react-hook-form": "7.51.5", + "react-is": "^18.2.0", "react-markdown": "^8.0.7", "react-masonry-component": "^6.3.0", "react-pdf-html": "^2.1.2", "react-popper": "^2.3.0", + "react-router-dom": "^7.9.1", + "react-router": "^7.9.1", + "react": "catalog:", "recharts": "^2.12.7", - "sharp": "catalog:", + "serve": "14.2.5", "smooth-scroll-into-view-if-needed": "^2.0.2", "swr": "catalog:", "tailwind-merge": "^2.0.0", @@ -71,12 +84,18 @@ "@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/react": "catalog:", "@types/react-color": "^3.0.6", "@types/react-dom": "catalog:", + "@types/react": "catalog:", "prettier": "^3.2.5", - "typescript": "catalog:" + "typescript": "catalog:", + "vite-tsconfig-paths": "^5.1.4", + "vite": "7.1.7" } } diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.cjs similarity index 99% rename from apps/web/postcss.config.js rename to apps/web/postcss.config.cjs index 9b1e55fc4..3764bc499 100644 --- a/apps/web/postcss.config.js +++ b/apps/web/postcss.config.cjs @@ -1,2 +1,3 @@ // eslint-disable-next-line @typescript-eslint/no-require-imports module.exports = require("@plane/tailwind-config/postcss.config.js"); + diff --git a/apps/web/public/animated-icons/uploading.json b/apps/web/public/animated-icons/uploading.json deleted file mode 100644 index 2258e72bc..000000000 --- a/apps/web/public/animated-icons/uploading.json +++ /dev/null @@ -1,742 +0,0 @@ -{ - "v": "5.3.4", - "fr": 60, - "ip": 0, - "op": 84, - "w": 678, - "h": 896, - "nm": "share", - "ddd": 0, - "assets": [], - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "Слой 18", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.347, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p347_1_0p333_0", - "t": 64, - "s": [338, 1288, 0], - "e": [338, 492, 0], - "to": [0, -132.66667175293, 0], - "ti": [0, 132.66667175293, 0] - }, - { "t": 76 } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 1, - "k": [ - { - "i": { "x": 0.354, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p354_1_0p333_0", - "t": 65, - "s": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [0.125, -433.461], - [0.223, -433.488], - [0, -433.488], - [0.229, -433.33], - [0, -433.488], - [0.25, -433.262] - ], - "c": false - } - ], - "e": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [177, -256.711], - [0.223, -433.488], - [0, -433.488], - [-176.777, -256.711], - [0, -433.488], - [0, 169.488] - ], - "c": false - } - ] - }, - { "t": 79 } - ], - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 3 }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 50, "ix": 5 }, - "lc": 2, - "lj": 2, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 65, - "op": 84, - "st": 63, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "Слой 15", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.393, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p393_1_0p333_0", - "t": 5, - "s": [338, 492, 0], - "e": [338, 482, 0], - "to": [0, -1.66666662693024, 0], - "ti": [0, -22.1666660308838, 0] - }, - { - "i": { "x": 0.418, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p418_1_0p333_0", - "t": 15, - "s": [338, 482, 0], - "e": [338, 625, 0], - "to": [0, 22.1666660308838, 0], - "ti": [0, -1.66666662693024, 0] - }, - { - "i": { "x": 0.362, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p362_1_0p333_0", - "t": 26, - "s": [338, 625, 0], - "e": [338, 492, 0], - "to": [0, 1.66666662693024, 0], - "ti": [0, 22.1666660308838, 0] - }, - { "t": 35 } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 1, - "k": [ - { - "i": { "x": 0.338, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p338_1_0p333_0", - "t": 0, - "s": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [177, -256.711], - [0.223, -433.488], - [0, -433.488], - [-176.777, -256.711], - [0, -433.488], - [0, 169.488] - ], - "c": false - } - ], - "e": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [169, -252.711], - [0.223, -433.488], - [0, -433.488], - [-168.777, -252.711], - [0, -433.488], - [0, 169.488] - ], - "c": false - } - ] - }, - { - "i": { "x": 0.368, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p368_1_0p333_0", - "t": 10, - "s": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [169, -252.711], - [0.223, -433.488], - [0, -433.488], - [-168.777, -252.711], - [0, -433.488], - [0, 169.488] - ], - "c": false - } - ], - "e": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [189, -181.274], - [0.223, -273], - [0, -273], - [-198.777, -181.274], - [0, -273], - [0, 169.488] - ], - "c": false - } - ] - }, - { - "i": { "x": 0.301, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p301_1_0p333_0", - "t": 21, - "s": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [189, -181.274], - [0.223, -273], - [0, -273], - [-198.777, -181.274], - [0, -273], - [0, 169.488] - ], - "c": false - } - ], - "e": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [177, -256.711], - [0.223, -433.488], - [0, -433.488], - [-176.777, -256.711], - [0, -433.488], - [0, 169.488] - ], - "c": false - } - ] - }, - { - "i": { "x": 0.338, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p338_1_0p333_0", - "t": 30, - "s": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [177, -256.711], - [0.223, -433.488], - [0, -433.488], - [-176.777, -256.711], - [0, -433.488], - [0, 169.488] - ], - "c": false - } - ], - "e": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [55, -331.686], - [0.223, -433.488], - [0, -433.488], - [-52.777, -331.843], - [0, -433.488], - [0, -78.512] - ], - "c": false - } - ] - }, - { - "i": { "x": 0.301, "y": 1 }, - "o": { "x": 0.333, "y": 0 }, - "n": "0p301_1_0p333_0", - "t": 36, - "s": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [55, -331.686], - [0.223, -433.488], - [0, -433.488], - [-52.777, -331.843], - [0, -433.488], - [0, -78.512] - ], - "c": false - } - ], - "e": [ - { - "i": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0], - [0, 0] - ], - "v": [ - [0.25, -432.593], - [0.223, -433.488], - [0, -433.488], - [-0.027, -433.093], - [0, -433.488], - [0, -432.512] - ], - "c": false - } - ] - }, - { "t": 42 } - ], - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 3 }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 50, "ix": 5 }, - "lc": 2, - "lj": 2, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 39, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "Слой 17", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [338, 492, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0], - [48, -30.133], - [32.954, 0], - [0, 0], - [25.447, 15.225], - [0, 61.848], - [0, 0] - ], - "o": [ - [0, 0], - [0, 60.546], - [-26.126, 16.401], - [0, 0], - [-31.652, 0], - [-49.724, -29.75], - [0, 0], - [0, 0] - ], - "v": [ - [301.777, 0], - [301.777, 196], - [221.699, 340.096], - [131.777, 366], - [-131.777, 366], - [-218.579, 342.008], - [-301.777, 196], - [-301.777, 0] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 3 }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 50, "ix": 5 }, - "lc": 2, - "lj": 2, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { - "a": 1, - "k": [ - { - "i": { "x": [0.833], "y": [1] }, - "o": { "x": [0.473], "y": [-0.005] }, - "n": ["0p833_1_0p473_-0p005"], - "t": 8, - "s": [0], - "e": [10] - }, - { - "i": { "x": [0.411], "y": [0.948] }, - "o": { "x": [0.167], "y": [0] }, - "n": ["0p411_0p948_0p167_0"], - "t": 19, - "s": [10], - "e": [0] - }, - { "t": 40 } - ], - "ix": 1 - }, - "e": { - "a": 1, - "k": [ - { - "i": { "x": [0.833], "y": [1] }, - "o": { "x": [0.473], "y": [0.005] }, - "n": ["0p833_1_0p473_0p005"], - "t": 8, - "s": [100], - "e": [90] - }, - { - "i": { "x": [0.411], "y": [1.052] }, - "o": { "x": [0.167], "y": [0] }, - "n": ["0p411_1p052_0p167_0"], - "t": 19, - "s": [90], - "e": [100] - }, - { "t": 40 } - ], - "ix": 2 - }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 0, - "op": 84, - "st": 0, - "bm": 0 - } - ], - "markers": [] -} diff --git a/apps/web/public/icons/icon-128x128.png b/apps/web/public/icons/icon-128x128.png deleted file mode 100644 index 57dc9ce58..000000000 Binary files a/apps/web/public/icons/icon-128x128.png and /dev/null differ diff --git a/apps/web/public/manifest.json b/apps/web/public/manifest.json index c7e2fc5af..35917737d 100644 --- a/apps/web/public/manifest.json +++ b/apps/web/public/manifest.json @@ -9,8 +9,8 @@ "purpose": "any maskable" }, { - "src": "/icons/icon-384x384.png", - "sizes": "384x384", + "src": "/icons/icon-348x348.png", + "sizes": "348x348", "type": "image/png" }, { diff --git a/apps/web/react-router.config.ts b/apps/web/react-router.config.ts new file mode 100644 index 000000000..8b10c5e4b --- /dev/null +++ b/apps/web/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_WEB_BASE_PATH, + // Web runs as a client-side app; build a static client bundle only + ssr: false, +} satisfies Config; diff --git a/apps/web/server.mjs b/apps/web/server.mjs new file mode 100644 index 000000000..7ffa0d605 --- /dev/null +++ b/apps/web/server.mjs @@ -0,0 +1,77 @@ +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_WEB_BASE_URL when available, otherwise +// default to http://localhost:3000 and fall back to PORT env if explicitly set. +const DEFAULT_BASE_URL = "http://localhost:3000"; +const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || DEFAULT_BASE_URL; +let parsedBaseUrl; +try { + parsedBaseUrl = new URL(WEB_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/web/server/app.ts b/apps/web/server/app.ts new file mode 100644 index 000000000..f2e7349f0 --- /dev/null +++ b/apps/web/server/app.ts @@ -0,0 +1,47 @@ +import "react-router"; +import { createRequestHandler } from "@react-router/express"; +import express from "express"; +import type { Express } from "express"; +import { createProxyMiddleware } from "http-proxy-middleware"; +import type { ServerBuild } from "react-router"; + +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_WEB_BASE_PATH = process.env.NEXT_PUBLIC_WEB_BASE_PATH + ? process.env.NEXT_PUBLIC_WEB_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") as Promise, + }) +); + +app.use(NEXT_PUBLIC_WEB_BASE_PATH, router); diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index 5fbc91843..509910022 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -970,3 +970,30 @@ div.web-view-spinner div.bar12 { border-radius: 4px; } } + +/* 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/web/tailwind.config.js b/apps/web/tailwind.config.cjs similarity index 99% rename from apps/web/tailwind.config.js rename to apps/web/tailwind.config.cjs index b6377a045..d1f2e7f4c 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.cjs @@ -5,3 +5,4 @@ const sharedConfig = require("@plane/tailwind-config/tailwind.config.js"); module.exports = { presets: [sharedConfig], }; + diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index f2589c8db..a49eef847 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,17 +1,18 @@ { - "extends": "@plane/typescript-config/nextjs.json", + "extends": "@plane/typescript-config/react-router.json", "compilerOptions": { "baseUrl": ".", + "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": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"], "exclude": ["node_modules"] } diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 000000000..c9bb4f2ae --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,62 @@ +import path from "node:path"; +import { reactRouter } from "@react-router/dev/vite"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +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", + "NEXT_PUBLIC_POSTHOG_KEY", + "NEXT_PUBLIC_POSTHOG_HOST", + "NEXT_PUBLIC_PLAUSIBLE_DOMAIN", + "NEXT_PUBLIC_ENABLE_SESSION_RECORDER", + "NEXT_PUBLIC_SESSION_RECORDER_KEY", +]; + +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.WEB_ENABLE_SSR_BUILD === "true"; + + return { + 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 web + "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"), + "next/script": path.resolve(__dirname, "app/compat/next/script.tsx"), + }, + dedupe: ["react", "react-dom", "@headlessui/react"], + }, + // No SSR-specific overrides needed; alias resolves to ESM build + }; +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3727381be..2d2a91543 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,9 +60,6 @@ catalogs: mobx-utils: specifier: 6.0.8 version: 6.0.8 - next: - specifier: 14.2.32 - version: 14.2.32 react: specifier: 18.3.1 version: 18.3.1 @@ -190,7 +187,7 @@ importers: 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) + version: 0.2.1(next@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 'catalog:' version: 18.3.1 @@ -389,7 +386,7 @@ importers: specifier: ^11.11.0 version: 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) '@headlessui/react': - specifier: ^1.7.13 + specifier: ^1.7.19 version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@plane/constants': specifier: workspace:* @@ -474,7 +471,7 @@ importers: 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) + version: 0.2.1(next@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 'catalog:' version: 18.3.1 @@ -560,11 +557,11 @@ importers: '@atlaskit/pragmatic-drag-and-drop-hitbox': specifier: 'catalog:' version: 1.1.0 - '@bprogress/next': - specifier: ^3.2.12 - version: 3.2.12(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) + '@bprogress/core': + specifier: 'catalog:' + version: 1.3.4 '@headlessui/react': - specifier: ^1.7.3 + specifier: ^1.7.19 version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@intercom/messenger-js-sdk': specifier: ^0.0.12 @@ -605,6 +602,12 @@ importers: '@react-pdf/renderer': specifier: ^3.4.5 version: 3.4.5(react@18.3.1) + '@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) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -620,11 +623,17 @@ importers: comlink: specifier: ^4.4.1 version: 4.4.2 + 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.0.3 + specifier: ^16.4.5 version: 16.6.1 emoji-picker-react: specifier: ^4.5.16 @@ -632,6 +641,15 @@ importers: export-to-csv: specifier: ^1.4.0 version: 1.4.0 + 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 @@ -647,12 +665,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) + version: 0.2.1(next@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) posthog-js: specifier: ^1.131.3 version: 1.255.1 @@ -668,9 +686,15 @@ importers: react-dropzone: specifier: ^14.2.3 version: 14.3.8(react@18.3.1) + react-fast-compare: + specifier: ^3.2.2 + version: 3.2.2 react-hook-form: specifier: 7.51.5 version: 7.51.5(react@18.3.1) + react-is: + specifier: ^18.2.0 + version: 18.3.1 react-markdown: specifier: ^8.0.7 version: 8.0.7(@types/react@18.3.11)(react@18.3.1) @@ -683,12 +707,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) + 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) recharts: specifier: ^2.12.7 version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - sharp: - specifier: 0.33.5 - version: 0.33.5 + serve: + specifier: 14.2.5 + version: 14.2.5 smooth-scroll-into-view-if-needed: specifier: ^2.0.2 version: 2.0.2 @@ -714,9 +744,21 @@ 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 @@ -735,6 +777,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)) packages/constants: dependencies: @@ -1704,19 +1752,6 @@ packages: '@bprogress/core@1.3.4': resolution: {integrity: sha512-q/AqpurI/1uJzOrQROuZWixn/+ARekh+uvJGwLCP6HQ/EqAX4SkvNf618tSBxL4NysC0MwqAppb/mRw6Tzi61w==} - '@bprogress/next@3.2.12': - resolution: {integrity: sha512-/ZvNwbAd0ty9QiQwCfT2AfwWVdAaEyCPx5RUz3CfiiJS/OLBohhDz/IC/srhwK9GnXeXavvtiUrpKzN5GJDwlw==} - peerDependencies: - next: '>=13.0.0' - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@bprogress/react@1.2.7': - resolution: {integrity: sha512-MqJfHW+R5CQeWqyqrLxUjdBRHk24Xl63OkBLo5DMWqUqocUikRTfCIc/jtQQbPk7BRfdr5OP3Lx7YlfQ9QOZMQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - '@chromatic-com/storybook@1.9.0': resolution: {integrity: sha512-vYQ+TcfktEE3GHnLZXHCzXF/sN9dw+KivH8a5cmPyd9YtQs7fZtHrEgsIjWpYycXiweKMo1Lm1RZsjxk8DH3rA==} engines: {node: '>=16.0.0', yarn: '>=1.22.18'} @@ -2084,111 +2119,6 @@ packages: peerDependencies: react: '*' - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@intercom/messenger-js-sdk@0.0.12': resolution: {integrity: sha512-xoUGlKLD8nIcZaH7AesR/LfwXH4QQUdPZMV4sApK/zvVFBgAY/A9IWp1ey/jUcp+776ejtZeEqreJZxG4LdEuw==} @@ -4727,10 +4657,6 @@ packages: color@3.2.1: resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -7809,10 +7735,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -9056,20 +8978,6 @@ snapshots: '@bprogress/core@1.3.4': {} - '@bprogress/next@3.2.12(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)': - dependencies: - '@bprogress/core': 1.3.4 - '@bprogress/react': 1.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next: 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) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@bprogress/react@1.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@bprogress/core': 1.3.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@chromatic-com/storybook@1.9.0(react@18.3.1)': dependencies: chromatic: 11.29.0 @@ -9467,81 +9375,6 @@ snapshots: dependencies: react: 18.3.1 - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true - - '@img/sharp-darwin-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - - '@img/sharp-wasm32@0.33.5': - dependencies: - '@emnapi/runtime': 1.5.0 - optional: true - - '@img/sharp-win32-ia32@0.33.5': - optional: true - - '@img/sharp-win32-x64@0.33.5': - optional: true - '@intercom/messenger-js-sdk@0.0.12': {} '@ioredis/commands@1.3.0': {} @@ -12496,11 +12329,6 @@ snapshots: color-convert: 1.9.3 color-string: 1.9.1 - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - colorette@2.0.20: {} colorspace@1.1.4: @@ -14683,7 +14511,7 @@ snapshots: neo-async@2.6.2: {} - next-themes@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): + next-themes@0.2.1(next@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: next: 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) react: 18.3.1 @@ -15982,32 +15810,6 @@ snapshots: setprototypeof@1.2.0: {} - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.1.0 - semver: 7.7.2 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0