[WEB-5043] feat: web vite migration (#7973)

This commit is contained in:
Prateek Shourya 2025-11-06 14:08:48 +05:30 committed by GitHub
parent 118ecc81ba
commit 696fb96e87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
642 changed files with 3013 additions and 2311 deletions

View file

@ -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 {

View file

@ -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 (
<div

View file

@ -1,5 +1,5 @@
import { lazy, Suspense } from "react";
import { observer } from "mobx-react";
import dynamic from "next/dynamic";
import { useParams } from "next/navigation";
import useSWR from "swr";
// plane package imports
@ -14,7 +14,7 @@ import { AnalyticsService } from "@/services/analytics.service";
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import { ProjectInsightsLoader } from "../loaders";
const RadarChart = dynamic(() =>
const RadarChart = lazy(() =>
import("@plane/propel/charts/radar-chart").then((mod) => ({
default: mod.RadarChart,
}))
@ -63,29 +63,31 @@ const ProjectInsights = observer(() => {
) : (
<div className="gap-8 lg:flex">
{projectInsightsData && (
<RadarChart
className="h-[350px] w-full lg:w-3/5"
data={projectInsightsData}
dataKey="key"
radars={[
{
key: "count",
name: "Count",
fill: "rgba(var(--color-primary-300))",
stroke: "rgba(var(--color-primary-300))",
fillOpacity: 0.6,
dot: {
r: 4,
fillOpacity: 1,
<Suspense fallback={<ProjectInsightsLoader />}>
<RadarChart
className="h-[350px] w-full lg:w-3/5"
data={projectInsightsData}
dataKey="key"
radars={[
{
key: "count",
name: "Count",
fill: "rgba(var(--color-primary-300))",
stroke: "rgba(var(--color-primary-300))",
fillOpacity: 0.6,
dot: {
r: 4,
fillOpacity: 1,
},
},
},
]}
margin={{ top: 0, right: 40, bottom: 10, left: 40 }}
showTooltip
angleAxis={{
key: "name",
}}
/>
]}
margin={{ top: 0, right: 40, bottom: 10, left: 40 }}
showTooltip
angleAxis={{
key: "name",
}}
/>
</Suspense>
)}
<div className="w-full lg:w-2/5">
<div className="text-sm text-custom-text-300">{t("workspace_analytics.summary_of_projects")}</div>

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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<Props> = observer((props) => {
const [query, setQuery] = useState("");
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
const [isSearching, setIsSearching] = useState(false);
// theme hook
const { resolvedTheme } = useTheme();
// hooks
const {
issues: { removeBulkIssues },
@ -52,8 +59,8 @@ export const BulkDeleteIssuesModal: React.FC<Props> = 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;

View file

@ -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<EmptyStateProps> = ({
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 }) => (
<div className="flex flex-col items-center justify-center px-3 py-8 text-center">{children}</div>

View file

@ -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={
<div className="flex items-center gap-2">
<div className="h-4 w-4 rounded-full border-2 border-custom-border-200 bg-custom-background-80">
<img src="/user.png" height="100%" width="100%" className="rounded-full" alt="User" />
<img src={userImage} height="100%" width="100%" className="rounded-full" alt="User" />
</div>
<span>{t("no_assignee")}</span>
</div>

View file

@ -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;

View file

@ -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<ActiveCycleStatsProps> = observer((props) => {
const issuesContainerRef = useRef<HTMLDivElement | null>(null);
// states
const [issuesLoaderElement, setIssueLoaderElement] = useState<HTMLDivElement | null>(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<ActiveCycleStatsProps> = observer((props) => {
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-custom-border-200 bg-custom-background-80">
<img src="/user.png" height="100%" width="100%" className="rounded-full" alt="User" />
<img src={userImage} height="100%" width="100%" className="rounded-full" alt="User" />
</div>
<span>{t("no_assignee")}</span>
</div>

View file

@ -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<ActiveCycleProductivityProps> = 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;

View file

@ -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<ActiveCycleProgressProps> = 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<ActiveCycleProgressProps> = 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") ? (
<div className="flex flex-col min-h-[17rem] gap-5 py-4 px-3.5 bg-custom-background-100 border border-custom-border-200 rounded-lg">

View file

@ -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;

View file

@ -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;

View file

@ -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" },
],
});

View file

@ -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<Props> = observer((props) => {
{description && <p className="text-sm">{description}</p>}
</div>
{assetPath && (
<Image src={assetPath} alt={title} width={384} height={250} layout="responsive" lazyBoundary="100%" />
)}
{assetPath && <img src={assetPath} alt={title} className="w-full h-auto" loading="lazy" />}
{hasButtons && (
<div className="relative flex items-center justify-center gap-2 flex-shrink-0 w-full">

View file

@ -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`);

View file

@ -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";

View file

@ -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;

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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> = (props) => {
const [query, setQuery] = useState("");
const [issues, setIssues] = useState<ISearchIssueResponse[]>([]);
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;

View file

@ -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<TInboxIssueRoot> = 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;

View file

@ -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

View file

@ -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();

View file

@ -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";

View file

@ -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";

View file

@ -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

View file

@ -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 = {

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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;

View file

@ -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<TWorkspaceDraftIssuesRoot> = 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);

View file

@ -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;

View file

@ -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";

View file

@ -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;
}[] = [

View file

@ -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;

View file

@ -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";

View file

@ -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;

View file

@ -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<Props> = observer((props) => {
const [isSubmitting, setIsSubmitting] = useState(false);
// refs
const moveButtonRef = useRef<HTMLButtonElement>(null);
// theme hook
const { resolvedTheme } = useTheme();
// plane hooks
const { t } = useTranslation();
// store hooks
@ -48,9 +52,7 @@ export const ProjectMultiSelectModal: React.FC<Props> = 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);

View file

@ -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 (

View file

@ -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 ? (
<Link href={`/${workspaceSlug}/settings/projects/${projectId}/features/${featureItem.href}`}>
<Link href={joinUrlPath(workspaceSlug, "settings", "projects", projectId, "features", featureItem.href)}>
<div className="flex items-center gap-2">
<Pill
variant={value ? EPillVariant.PRIMARY : EPillVariant.DEFAULT}

View file

@ -6,19 +6,24 @@ import type {
import type { ElementDragPayload } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { useTheme } from "next-themes";
import Masonry from "react-masonry-component";
import { Plus } from "lucide-react";
// plane imports
import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { EUserWorkspaceRoles } from "@plane/types";
// assets
import darkStickiesAsset from "@/app/assets/empty-state/stickies/stickies-dark.webp?url";
import lightStickiesAsset from "@/app/assets/empty-state/stickies/stickies-light.webp?url";
import darkStickiesSearchAsset from "@/app/assets/empty-state/stickies/stickies-search-dark.webp?url";
import lightStickiesSearchAsset from "@/app/assets/empty-state/stickies/stickies-search-light.webp?url";
// components
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { SimpleEmptyState } from "@/components/empty-state/simple-empty-state-root";
import { StickiesEmptyState } from "@/components/home/widgets/empty-states/stickies";
// hooks
import { useUserPermissions } from "@/hooks/store/user";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
import { useSticky } from "@/hooks/use-stickies";
// local imports
import { useStickyOperations } from "../sticky/use-operations";
@ -39,6 +44,8 @@ export const StickiesList = observer((props: TProps) => {
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<any>(null);
const handleLayout = () => {

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -1,4 +1,4 @@
// router from n-progress-bar
import { useRouter } from "@/lib/b-progress";
import { useRouter } from "next/navigation";
export const useAppRouter = () => useRouter();

View file

@ -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}`;
};

View file

@ -32,7 +32,7 @@ import { persistence } from "@/local-db/storage.sqlite";
interface IProjectAuthWrapper {
workspaceSlug: string;
projectId: string;
projectId?: string;
children: ReactNode;
isLoading?: boolean;
}

View file

@ -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;

View file

@ -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<ProgressConfig> = {
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 (
* <>
* <AppProgressBar />
* <Outlet />
* </>
* );
* }
* ```
*/
export function AppProgressBar(): null {
const navigation = useNavigation();
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const startedRef = useRef<boolean>(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;
}

View file

@ -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<IPosthogWrapper> = observer((props) => {
if (is_posthog_enabled)
return (
<PHProvider client={posthog}>
<PostHogPageView />
<Suspense>
<PostHogPageView />
</Suspense>
{children}
</PHProvider>
);

View file

@ -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;
/**