[WEB-5459] feat(codemods): add function declaration transformer with tests (#8137)

- Add jscodeshift-based codemod to convert arrow function components to function declarations
- Support React.FC, observer-wrapped, and forwardRef components
- Include comprehensive test suite covering edge cases
- Add npm script to run transformer across codebase
- Target only .tsx files in source directories, excluding node_modules and declaration files

* [WEB-5459] chore: updates after running codemod

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Aaron 2025-11-20 19:09:40 +07:00 committed by GitHub
parent 90866fb925
commit 83fdebf64d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1771 changed files with 17003 additions and 13856 deletions

View file

@ -11,7 +11,7 @@ type TAuthBanner = {
handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
};
export const AuthBanner: FC<TAuthBanner> = (props) => {
export function AuthBanner(props: TAuthBanner) {
const { bannerData, handleBannerData } = props;
// translation
const { t } = useTranslation();
@ -37,4 +37,4 @@ export const AuthBanner: FC<TAuthBanner> = (props) => {
</button>
</div>
);
};
}

View file

@ -52,7 +52,7 @@ const Titles = {
const workSpaceService = new WorkspaceService();
export const AuthHeader: FC<TAuthHeader> = observer((props) => {
export const AuthHeader = observer(function AuthHeader(props: TAuthHeader) {
const { workspaceSlug, invitationId, invitationEmail, authMode, currentAuthStep } = props;
// plane imports
const { t } = useTranslation();

View file

@ -32,7 +32,7 @@ type TAuthRoot = {
authMode: EAuthModes;
};
export const AuthRoot: FC<TAuthRoot> = observer((props) => {
export const AuthRoot = observer(function AuthRoot(props: TAuthRoot) {
//router
const searchParams = useSearchParams();
// query params

View file

@ -1,7 +1,9 @@
"use client";
export const FormContainer = ({ children }: { children: React.ReactNode }) => (
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">{children}</div>
</div>
);
export function FormContainer({ children }: { children: React.ReactNode }) {
return (
<div className="flex flex-col justify-center items-center flex-grow w-full py-6 mt-10">
<div className="relative flex flex-col gap-6 max-w-[22.5rem] w-full">{children}</div>
</div>
);
}

View file

@ -1,8 +1,10 @@
"use client";
export const AuthFormHeader = ({ title, description }: { title: string; description: string }) => (
<div className="flex flex-col gap-1">
<span className="text-2xl font-semibold text-custom-text-100">{title}</span>
<span className="text-2xl font-semibold text-custom-text-400">{description}</span>
</div>
);
export function AuthFormHeader({ title, description }: { title: string; description: string }) {
return (
<div className="flex flex-col gap-1">
<span className="text-2xl font-semibold text-custom-text-100">{title}</span>
<span className="text-2xl font-semibold text-custom-text-400">{description}</span>
</div>
);
}

View file

@ -17,7 +17,7 @@ type TAuthEmailForm = {
onSubmit: (data: IEmailCheckData) => Promise<void>;
};
export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailForm) {
const { onSubmit, defaultEmail } = props;
// states
const [isSubmitting, setIsSubmitting] = useState(false);

View file

@ -5,7 +5,7 @@ import { Popover } from "@headlessui/react";
import { useTranslation } from "@plane/i18n";
import { CloseIcon } from "@plane/propel/icons";
export const ForgotPasswordPopover = () => {
export function ForgotPasswordPopover() {
// popper-js refs
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
@ -58,4 +58,4 @@ export const ForgotPasswordPopover = () => {
</Popover.Panel>
</Popover>
);
};
}

View file

@ -34,7 +34,7 @@ const defaultValues: TForgotPasswordFormValues = {
// services
const authService = new AuthService();
export const ForgotPasswordForm = observer(() => {
export const ForgotPasswordForm = observer(function ForgotPasswordForm() {
// search params
const searchParams = useSearchParams();
const email = searchParams.get("email");

View file

@ -31,7 +31,7 @@ type TAuthFormRoot = {
const authService = new AuthService();
export const AuthFormRoot = observer((props: TAuthFormRoot) => {
export const AuthFormRoot = observer(function AuthFormRoot(props: TAuthFormRoot) {
const { authStep, authMode, email, setEmail, setAuthMode, setAuthStep, setErrorInfo, currentAuthMode } = props;
// router
const router = useAppRouter();

View file

@ -44,7 +44,7 @@ const defaultValues: TPasswordFormValues = {
const authService = new AuthService();
export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props) {
const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode, nextPath } = props;
// plane imports
const { t } = useTranslation();

View file

@ -36,7 +36,7 @@ const defaultValues: TResetPasswordFormValues = {
// services
const authService = new AuthService();
export const ResetPasswordForm = observer(() => {
export const ResetPasswordForm = observer(function ResetPasswordForm() {
// search params
const searchParams = useSearchParams();
const uidb64 = searchParams.get("uidb64");

View file

@ -39,7 +39,7 @@ const defaultValues: TResetPasswordFormValues = {
// services
const authService = new AuthService();
export const SetPasswordForm = observer(() => {
export const SetPasswordForm = observer(function SetPasswordForm() {
// router
const router = useAppRouter();
// search params

View file

@ -37,7 +37,7 @@ const defaultValues: TUniqueCodeFormValues = {
code: "",
};
export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
export function AuthUniqueCodeForm(props: TAuthUniqueCodeForm) {
const { mode, email, handleEmailClear, generateEmailUniqueCode, isExistingEmail, nextPath } = props;
// derived values
const defaultResetTimerValue = 5;
@ -205,4 +205,4 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
</div>
</form>
);
};
}

View file

@ -18,7 +18,7 @@ type Props = {
onClose: () => void;
};
export const DeactivateAccountModal: React.FC<Props> = (props) => {
export function DeactivateAccountModal(props: Props) {
const router = useAppRouter();
const { isOpen, onClose } = props;
// hooks
@ -125,4 +125,4 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
</Dialog>
</Transition.Root>
);
};
}

View file

@ -18,18 +18,22 @@ const MESSAGES = {
} as const;
// Reusable link component to reduce duplication
const LegalLink: React.FC<{ href: string; children: React.ReactNode }> = ({ href, children }) => (
<Link href={href} className="text-custom-text-200" target="_blank" rel="noopener noreferrer">
<span className="text-sm font-medium underline hover:cursor-pointer">{children}</span>
</Link>
);
function LegalLink({ href, children }: { href: string; children: React.ReactNode }) {
return (
<Link href={href} className="text-custom-text-200" target="_blank" rel="noopener noreferrer">
<span className="text-sm font-medium underline hover:cursor-pointer">{children}</span>
</Link>
);
}
export const TermsAndConditions: React.FC<TermsAndConditionsProps> = ({ authType = EAuthModes.SIGN_IN }) => (
<div className="flex items-center justify-center">
<p className="text-center text-sm text-custom-text-300 whitespace-pre-line">
{`${MESSAGES[authType]}, you understand and agree to \n our `}
<LegalLink href={LEGAL_LINKS.termsOfService}>Terms of Service</LegalLink> and{" "}
<LegalLink href={LEGAL_LINKS.privacyPolicy}>Privacy Policy</LegalLink>.
</p>
</div>
);
export function TermsAndConditions({ authType = EAuthModes.SIGN_IN }: TermsAndConditionsProps) {
return (
<div className="flex items-center justify-center">
<p className="text-center text-sm text-custom-text-300 whitespace-pre-line">
{`${MESSAGES[authType]}, you understand and agree to \n our `}
<LegalLink href={LEGAL_LINKS.termsOfService}>Terms of Service</LegalLink> and{" "}
<LegalLink href={LEGAL_LINKS.privacyPolicy}>Privacy Policy</LegalLink>.
</p>
</div>
);
}

View file

@ -7,7 +7,7 @@ import { useProject } from "@/hooks/store/use-project";
import DurationDropdown from "./select/duration";
import { ProjectSelect } from "./select/project";
const AnalyticsFilterActions = observer(() => {
const AnalyticsFilterActions = observer(function AnalyticsFilterActions() {
const { selectedProjects, selectedDuration, updateSelectedProjects, updateSelectedDuration } = useAnalytics();
const { joinedProjectIds } = useProject();
return (

View file

@ -9,7 +9,7 @@ type Props = {
headerClassName?: string;
};
const AnalyticsSectionWrapper: React.FC<Props> = (props) => {
function AnalyticsSectionWrapper(props: Props) {
const { title, children, className, subtitle, actions, headerClassName } = props;
return (
<div className={className}>
@ -25,6 +25,6 @@ const AnalyticsSectionWrapper: React.FC<Props> = (props) => {
{children}
</div>
);
};
}
export default AnalyticsSectionWrapper;

View file

@ -9,7 +9,7 @@ type Props = {
className?: string;
};
const AnalyticsWrapper: React.FC<Props> = (props) => {
function AnalyticsWrapper(props: Props) {
const { i18nTitle, children, className } = props;
const { t } = useTranslation();
return (
@ -18,6 +18,6 @@ const AnalyticsWrapper: React.FC<Props> = (props) => {
{children}
</div>
);
};
}
export default AnalyticsWrapper;

View file

@ -12,7 +12,7 @@ type Props = {
className?: string;
};
const AnalyticsEmptyState = ({ title, description, assetPath, className }: Props) => {
function AnalyticsEmptyState({ title, description, assetPath, className }: Props) {
// theme hook
const { resolvedTheme } = useTheme();
const backgroundReolvedPath = resolvedTheme === "light" ? lightBackgroundAsset : darkBackgroundAsset;
@ -40,5 +40,6 @@ const AnalyticsEmptyState = ({ title, description, assetPath, className }: Props
</div>
</div>
);
};
}
export default AnalyticsEmptyState;

View file

@ -9,7 +9,7 @@ export type InsightCardProps = {
isLoading?: boolean;
};
const InsightCard = (props: InsightCardProps) => {
function InsightCard(props: InsightCardProps) {
const { data, label, isLoading = false } = props;
const count = data?.count ?? 0;
@ -25,6 +25,6 @@ const InsightCard = (props: InsightCardProps) => {
)}
</div>
);
};
}
export default InsightCard;

View file

@ -8,27 +8,29 @@ interface TableSkeletonProps {
rows: number;
}
export const TableLoader: React.FC<TableSkeletonProps> = ({ columns, rows }) => (
<Table>
<TableHeader>
<TableRow>
{columns.map((column, index) => (
<TableHead key={column.header?.toString() ?? index}>
{typeof column.header === "string" ? column.header : ""}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{Array.from({ length: rows }).map((_, rowIndex) => (
<TableRow key={rowIndex}>
{columns.map((_, colIndex) => (
<TableCell key={colIndex}>
<Loader.Item height="20px" width="100%" />
</TableCell>
export function TableLoader({ columns, rows }: TableSkeletonProps) {
return (
<Table>
<TableHeader>
<TableRow>
{columns.map((column, index) => (
<TableHead key={column.header?.toString() ?? index}>
{typeof column.header === "string" ? column.header : ""}
</TableHead>
))}
</TableRow>
))}
</TableBody>
</Table>
);
</TableHeader>
<TableBody>
{Array.from({ length: rows }).map((_, rowIndex) => (
<TableRow key={rowIndex}>
{columns.map((_, colIndex) => (
<TableCell key={colIndex}>
<Loader.Item height="20px" width="100%" />
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
);
}

View file

@ -15,9 +15,9 @@ interface InsightTableProps<T extends Exclude<TAnalyticsTabsBase, "overview">> {
onExport?: (rows: Row<AnalyticsTableDataMap[T]>[]) => void;
}
export const InsightTable = <T extends Exclude<TAnalyticsTabsBase, "overview">>(
export function InsightTable<T extends Exclude<TAnalyticsTabsBase, "overview">>(
props: InsightTableProps<T>
): React.ReactElement => {
): React.ReactElement {
const { data, isLoading, columns, headerText, onExport } = props;
const { t } = useTranslation();
if (isLoading) {
@ -42,4 +42,4 @@ export const InsightTable = <T extends Exclude<TAnalyticsTabsBase, "overview">>(
/>
</div>
);
};
}

View file

@ -1,23 +1,27 @@
import { Loader } from "@plane/ui";
export const ProjectInsightsLoader = () => (
<div className="flex h-[200px] gap-1">
<Loader className="h-full w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
<div className="flex h-full w-full flex-col gap-1">
<Loader className="h-12 w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
export function ProjectInsightsLoader() {
return (
<div className="flex h-[200px] gap-1">
<Loader className="h-full w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
<div className="flex h-full w-full flex-col gap-1">
<Loader className="h-12 w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
<Loader className="h-full w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
</div>
</div>
</div>
);
);
}
export const ChartLoader = () => (
<Loader className="h-[350px] w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
);
export function ChartLoader() {
return (
<Loader className="h-[350px] w-full">
<Loader.Item height="100%" width="100%" />
</Loader>
);
}

View file

@ -13,16 +13,17 @@ type Props = {
};
isLoading?: boolean;
};
const CompletionPercentage = ({ percentage }: { percentage: number }) => {
function CompletionPercentage({ percentage }: { percentage: number }) {
const percentageColor = percentage > 50 ? "bg-green-500/30 text-green-500" : "bg-red-500/30 text-red-500";
return (
<div className={cn("flex items-center gap-2 rounded p-1 text-xs", percentageColor)}>
<span>{percentage}%</span>
</div>
);
};
}
const ActiveProjectItem = (props: Props) => {
function ActiveProjectItem(props: Props) {
const { project } = props;
const { getProjectById } = useProject();
const { id, completed_issues, total_issues } = project;
@ -52,6 +53,6 @@ const ActiveProjectItem = (props: Props) => {
/>
</div>
);
};
}
export default ActiveProjectItem;

View file

@ -12,7 +12,7 @@ import { useProject } from "@/hooks/store/use-project";
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import ActiveProjectItem from "./active-project-item";
const ActiveProjects = observer(() => {
const ActiveProjects = observer(function ActiveProjects() {
const { t } = useTranslation();
const { fetchProjectAnalyticsCount } = useProject();
const { workspaceSlug } = useParams();

View file

@ -14,15 +14,15 @@ import { AnalyticsService } from "@/services/analytics.service";
import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import { ProjectInsightsLoader } from "../loaders";
const RadarChart = lazy(() =>
import("@plane/propel/charts/radar-chart").then((mod) => ({
const RadarChart = lazy(function RadarChart() {
return import("@plane/propel/charts/radar-chart").then((mod) => ({
default: mod.RadarChart,
}))
);
}));
});
const analyticsService = new AnalyticsService();
const ProjectInsights = observer(() => {
const ProjectInsights = observer(function ProjectInsights() {
const params = useParams();
const { t } = useTranslation();
const workspaceSlug = params.workspaceSlug.toString();

View file

@ -4,16 +4,18 @@ import TotalInsights from "../total-insights";
import ActiveProjects from "./active-projects";
import ProjectInsights from "./project-insights";
const Overview: React.FC = () => (
<AnalyticsWrapper i18nTitle="common.overview">
<div className="flex flex-col gap-14">
<TotalInsights analyticsType="overview" />
<div className="grid grid-cols-1 gap-14 md:grid-cols-5 ">
<ProjectInsights />
<ActiveProjects />
function Overview() {
return (
<AnalyticsWrapper i18nTitle="common.overview">
<div className="flex flex-col gap-14">
<TotalInsights analyticsType="overview" />
<div className="grid grid-cols-1 gap-14 md:grid-cols-5 ">
<ProjectInsights />
<ActiveProjects />
</div>
</div>
</div>
</AnalyticsWrapper>
);
</AnalyticsWrapper>
);
}
export { Overview };

View file

@ -21,7 +21,7 @@ type Props = {
isEpic?: boolean;
};
export const AnalyticsSelectParams: React.FC<Props> = observer((props) => {
export const AnalyticsSelectParams = observer(function AnalyticsSelectParams(props: Props) {
const { control, params, classNames, isEpic } = props;
const xAxisOptions = useMemo(
() => ANALYTICS_X_AXIS_VALUES.filter((option) => option.value !== params.group_by),

View file

@ -14,7 +14,7 @@ type Props = {
projectIds: string[] | undefined;
};
export const ProjectSelect: React.FC<Props> = observer((props) => {
export const ProjectSelect = observer(function ProjectSelect(props: Props) {
const { value, onChange, projectIds } = props;
const { getProjectById } = useProject();

View file

@ -13,7 +13,7 @@ type Props = {
label?: string | React.ReactNode;
};
export const SelectXAxis: React.FC<Props> = (props) => {
export function SelectXAxis(props: Props) {
const { value, onChange, options, hiddenOptions, allowNoValue, label } = props;
return (
<CustomSelect value={value} label={label} onChange={onChange} maxHeight="lg">
@ -28,4 +28,4 @@ export const SelectXAxis: React.FC<Props> = (props) => {
})}
</CustomSelect>
);
};
}

View file

@ -17,7 +17,7 @@ type Props = {
options: { value: ChartYAxisMetric; label: string }[];
};
export const SelectYAxis: React.FC<Props> = observer(({ value, onChange, hiddenOptions, options }) => {
export const SelectYAxis = observer(function SelectYAxis({ value, onChange, hiddenOptions, options }: Props) {
// hooks
const { projectId } = useParams();
const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates();

View file

@ -44,10 +44,13 @@ const getInsightLabel = (
return `${prefix}${baseTranslation}${suffix}`;
};
const TotalInsights: React.FC<{
const TotalInsights = observer(function TotalInsights({
analyticsType,
peekView,
}: {
analyticsType: TAnalyticsTabsBase;
peekView?: boolean;
}> = observer(({ analyticsType, peekView }) => {
}) {
const params = useParams();
const workspaceSlug = params.workspaceSlug.toString();
const { t } = useTranslation();

View file

@ -49,7 +49,7 @@ const variants: Record<NonNullable<Props["variant"]>, Record<"ontrack" | "offtra
},
} as const;
const TrendPiece = (props: Props) => {
function TrendPiece(props: Props) {
const { percentage, className, trendIconVisible = true, size = "sm", variant = "simple" } = props;
const isOnTrack = percentage >= 66;
const isOffTrack = percentage >= 33 && percentage < 66;
@ -75,6 +75,6 @@ const TrendPiece = (props: Props) => {
{Math.round(Math.abs(percentage))}%
</div>
);
};
}
export default TrendPiece;

View file

@ -17,7 +17,7 @@ import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import { ChartLoader } from "../loaders";
const analyticsService = new AnalyticsService();
const CreatedVsResolved = observer(() => {
const CreatedVsResolved = observer(function CreatedVsResolved() {
const {
selectedDuration,
selectedDurationLabel,

View file

@ -11,7 +11,13 @@ import AnalyticsSectionWrapper from "../analytics-section-wrapper";
import { AnalyticsSelectParams } from "../select/analytics-params";
import PriorityChart from "./priority-chart";
const CustomizedInsights = observer(({ peekView, isEpic }: { peekView?: boolean; isEpic?: boolean }) => {
const CustomizedInsights = observer(function CustomizedInsights({
peekView,
isEpic,
}: {
peekView?: boolean;
isEpic?: boolean;
}) {
const { t } = useTranslation();
const { workspaceSlug } = useParams();
const { control, watch, setValue } = useForm<IAnalyticsParams>({

View file

@ -20,7 +20,7 @@ type Props = {
isEpic?: boolean;
};
export const WorkItemsModalMainContent: React.FC<Props> = observer((props) => {
export const WorkItemsModalMainContent = observer(function WorkItemsModalMainContent(props: Props) {
const { projectDetails, cycleDetails, moduleDetails, fullScreen, isEpic } = props;
const { updateSelectedProjects, updateSelectedCycle, updateSelectedModule, updateIsPeekView } = useAnalytics();
const [isModalConfigured, setIsModalConfigured] = useState(false);

View file

@ -14,7 +14,7 @@ type Props = {
module?: IModule;
};
export const WorkItemsModalHeader: React.FC<Props> = observer((props) => {
export const WorkItemsModalHeader = observer(function WorkItemsModalHeader(props: Props) {
const { fullScreen, handleClose, setFullScreen, title, cycle, module } = props;
return (

View file

@ -17,7 +17,7 @@ type Props = {
isEpic?: boolean;
};
export const WorkItemsModal: React.FC<Props> = observer((props) => {
export const WorkItemsModal = observer(function WorkItemsModal(props: Props) {
const { isOpen, onClose, projectDetails, moduleDetails, cycleDetails, isEpic } = props;
const { updateIsEpic, isPeekView } = useAnalytics();
const [fullScreen, setFullScreen] = useState(false);

View file

@ -42,7 +42,7 @@ interface Props {
}
const analyticsService = new AnalyticsService();
const PriorityChart = observer((props: Props) => {
const PriorityChart = observer(function PriorityChart(props: Props) {
const { x_axis, y_axis, group_by } = props;
const { t } = useTranslation();
// store hooks

View file

@ -5,15 +5,17 @@ import CreatedVsResolved from "./created-vs-resolved";
import CustomizedInsights from "./customized-insights";
import WorkItemsInsightTable from "./workitems-insight-table";
const WorkItems: React.FC = () => (
<AnalyticsWrapper i18nTitle="sidebar.work_items">
<div className="flex flex-col gap-14">
<TotalInsights analyticsType="work-items" />
<CreatedVsResolved />
<CustomizedInsights />
<WorkItemsInsightTable />
</div>
</AnalyticsWrapper>
);
function WorkItems() {
return (
<AnalyticsWrapper i18nTitle="sidebar.work_items">
<div className="flex flex-col gap-14">
<TotalInsights analyticsType="work-items" />
<CreatedVsResolved />
<CustomizedInsights />
<WorkItemsInsightTable />
</div>
</AnalyticsWrapper>
);
}
export { WorkItems };

View file

@ -32,7 +32,7 @@ declare module "@tanstack/react-table" {
}
}
const WorkItemsInsightTable = observer(() => {
const WorkItemsInsightTable = observer(function WorkItemsInsightTable() {
// router
const params = useParams();
const workspaceSlug = params.workspaceSlug.toString();

View file

@ -23,7 +23,7 @@ type Props = {
const apiTokenService = new APITokenService();
export const DeleteApiTokenModal: FC<Props> = (props) => {
export function DeleteApiTokenModal(props: Props) {
const { isOpen, onClose, tokenId } = props;
// states
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
@ -90,4 +90,4 @@ export const DeleteApiTokenModal: FC<Props> = (props) => {
content={<>{t("workspace_settings.settings.api_tokens.delete.description")} </>}
/>
);
};
}

View file

@ -10,7 +10,7 @@ type Props = {
onClick: () => void;
};
export const ApiTokenEmptyState: React.FC<Props> = (props) => {
export function ApiTokenEmptyState(props: Props) {
const { onClick } = props;
return (
@ -29,4 +29,4 @@ export const ApiTokenEmptyState: React.FC<Props> = (props) => {
</div>
</div>
);
};
}

View file

@ -25,7 +25,7 @@ type Props = {
// services
const apiTokenService = new APITokenService();
export const CreateApiTokenModal: React.FC<Props> = (props) => {
export function CreateApiTokenModal(props: Props) {
const { isOpen, onClose } = props;
// states
const [neverExpires, setNeverExpires] = useState<boolean>(false);
@ -104,4 +104,4 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
)}
</ModalCore>
);
};
}

View file

@ -66,7 +66,7 @@ const getFormattedDate = (date: Date): Date => {
return add(date, { hours, minutes, seconds });
};
export const CreateApiTokenForm: React.FC<Props> = (props) => {
export function CreateApiTokenForm(props: Props) {
const { handleClose, neverExpires, toggleNeverExpires, onSubmit } = props;
// states
const [customDate, setCustomDate] = useState<Date | null>(null);
@ -253,4 +253,4 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
</div>
</form>
);
};
}

View file

@ -18,7 +18,7 @@ type Props = {
tokenDetails: IApiToken;
};
export const GeneratedTokenDetails: React.FC<Props> = (props) => {
export function GeneratedTokenDetails(props: Props) {
const { handleClose, tokenDetails } = props;
const { isMobile } = usePlatformOS();
const { t } = useTranslation();
@ -60,4 +60,4 @@ export const GeneratedTokenDetails: React.FC<Props> = (props) => {
</div>
</div>
);
};
}

View file

@ -16,7 +16,7 @@ type Props = {
token: IApiToken;
};
export const ApiTokenListItem: React.FC<Props> = (props) => {
export function ApiTokenListItem(props: Props) {
const { token } = props;
// states
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
@ -61,4 +61,4 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
</div>
</>
);
};
}

View file

@ -29,7 +29,7 @@ const ARCHIVES_TAB_LIST: {
},
];
export const ArchiveTabsList: FC = observer(() => {
export const ArchiveTabsList = observer(function ArchiveTabsList() {
// router
const { workspaceSlug, projectId } = useParams();
const pathname = usePathname();

View file

@ -9,10 +9,12 @@ type AuthBaseProps = {
authType: EAuthModes;
};
export const AuthBase = ({ authType }: AuthBaseProps) => (
<div className="relative z-10 flex flex-col items-center w-screen h-screen overflow-hidden overflow-y-auto pt-6 pb-10 px-8">
<AuthHeader type={authType} />
<AuthRoot authMode={authType} />
<AuthFooter />
</div>
);
export function AuthBase({ authType }: AuthBaseProps) {
return (
<div className="relative z-10 flex flex-col items-center w-screen h-screen overflow-hidden overflow-y-auto pt-6 pb-10 px-8">
<AuthHeader type={authType} />
<AuthRoot authMode={authType} />
<AuthFooter />
</div>
);
}

View file

@ -24,15 +24,17 @@ const BRAND_LOGOS: {
},
];
export const AuthFooter = () => (
<div className="flex flex-col items-center gap-6">
<span className="text-sm text-custom-text-300 whitespace-nowrap">Join 10,000+ teams building with Plane</span>
<div className="flex items-center justify-center gap-x-10 gap-y-4 w-full flex-wrap">
{BRAND_LOGOS.map((brand) => (
<div className="flex items-center justify-center h-7 flex-1" key={brand.id}>
{brand.icon}
</div>
))}
export function AuthFooter() {
return (
<div className="flex flex-col items-center gap-6">
<span className="text-sm text-custom-text-300 whitespace-nowrap">Join 10,000+ teams building with Plane</span>
<div className="flex items-center justify-center gap-x-10 gap-y-4 w-full flex-wrap">
{BRAND_LOGOS.map((brand) => (
<div className="flex items-center justify-center h-7 flex-1" key={brand.id}>
{brand.icon}
</div>
))}
</div>
</div>
</div>
);
);
}

View file

@ -29,7 +29,7 @@ type AuthHeaderProps = {
type: EAuthModes;
};
export const AuthHeader = observer(({ type }: AuthHeaderProps) => {
export const AuthHeader = observer(function AuthHeader({ type }: AuthHeaderProps) {
const { t } = useTranslation();
// store
const { config } = useInstance();

View file

@ -14,7 +14,7 @@ type Props = {
className?: string;
};
export const NotAuthorizedView: React.FC<Props> = observer((props) => {
export const NotAuthorizedView = observer(function NotAuthorizedView(props: Props) {
const { actionButton, section = "general", isProjectView = false, className } = props;
// assets

View file

@ -15,7 +15,7 @@ type Props = {
isPrivateProject?: boolean;
};
export const JoinProject: React.FC<Props> = (props) => {
export function JoinProject(props: Props) {
const { projectId, isPrivateProject = false } = props;
// states
const [isJoiningProject, setIsJoiningProject] = useState(false);
@ -65,4 +65,4 @@ export const JoinProject: React.FC<Props> = (props) => {
)}
</div>
);
};
}

View file

@ -6,30 +6,32 @@ import { Button } from "@plane/propel/button";
// layouts
import DefaultLayout from "@/layouts/default-layout";
export const NotAWorkspaceMember = () => (
<DefaultLayout>
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Not Authorized!</h3>
<p className="mx-auto w-1/2 text-sm text-custom-text-200">
You{"'"}re not a member of this workspace. Please contact the workspace admin to get an invitation or check
your pending invitations.
</p>
</div>
<div className="flex items-center justify-center gap-2">
<Link href="/invitations">
<span>
<Button variant="neutral-primary">Check pending invites</Button>
</span>
</Link>
<Link href="/create-workspace">
<span>
<Button variant="primary">Create new workspace</Button>
</span>
</Link>
export function NotAWorkspaceMember() {
return (
<DefaultLayout>
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Not Authorized!</h3>
<p className="mx-auto w-1/2 text-sm text-custom-text-200">
You{"'"}re not a member of this workspace. Please contact the workspace admin to get an invitation or
check your pending invitations.
</p>
</div>
<div className="flex items-center justify-center gap-2">
<Link href="/invitations">
<span>
<Button variant="neutral-primary">Check pending invites</Button>
</span>
</Link>
<Link href="/create-workspace">
<span>
<Button variant="primary">Create new workspace</Button>
</span>
</Link>
</div>
</div>
</div>
</div>
</DefaultLayout>
);
</DefaultLayout>
);
}

View file

@ -30,7 +30,7 @@ type Props = {
const initialValues: Partial<IProject> = { archive_in: 1 };
export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
export const AutoArchiveAutomation = observer(function AutoArchiveAutomation(props: Props) {
const { handleChange } = props;
// router
const { workspaceSlug } = useParams();

View file

@ -32,7 +32,7 @@ type Props = {
handleChange: (formData: Partial<IProject>) => Promise<void>;
};
export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
export const AutoCloseAutomation = observer(function AutoCloseAutomation(props: Props) {
const { handleChange } = props;
// router
const { workspaceSlug } = useParams();

View file

@ -21,7 +21,7 @@ type Props = {
handleChange: (formData: Partial<IProject>) => Promise<void>;
};
export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen, handleClose, handleChange }) => {
export function SelectMonthModal({ type, initialValues, isOpen, handleClose, handleChange }: Props) {
const { workspaceSlug, projectId } = useParams();
const {
@ -165,4 +165,4 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
</Dialog>
</Transition.Root>
);
};
}

View file

@ -14,7 +14,9 @@ import { TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { GanttChartRoot } from "@/components/gantt-chart/root";
import { BaseGanttSidebar } from "./sidebar";
export const BaseGanttLayout = observer(<T extends IBaseLayoutsGanttItem>(props: IBaseLayoutsGanttProps<T>) => {
export const BaseGanttLayout = observer(function BaseGanttLayout<T extends IBaseLayoutsGanttItem>(
props: IBaseLayoutsGanttProps<T>
) {
const {
items,
groupedItemIds,

View file

@ -26,7 +26,7 @@ type Props<T extends IBaseLayoutsBaseItem> = {
renderItem: (item: T) => React.ReactNode;
};
export const BaseGanttSidebar = observer(<T extends IBaseLayoutsBaseItem>(props: Props<T>) => {
export const BaseGanttSidebar = observer(function BaseGanttSidebar<T extends IBaseLayoutsBaseItem>(props: Props<T>) {
const {
blockUpdateHandler,
blockIds,

View file

@ -1,14 +1,16 @@
import type { IGroupHeaderProps } from "@plane/types";
export const GroupHeader = ({ group, itemCount, onToggleGroup }: IGroupHeaderProps) => (
<button
onClick={() => onToggleGroup(group.id)}
className="flex w-full items-center gap-2 text-sm font-medium text-custom-text-200"
>
<div className="flex items-center gap-2">
{group.icon}
<span>{group.name}</span>
</div>
<span className="text-xs text-custom-text-300">{itemCount}</span>
</button>
);
export function GroupHeader({ group, itemCount, onToggleGroup }: IGroupHeaderProps) {
return (
<button
onClick={() => onToggleGroup(group.id)}
className="flex w-full items-center gap-2 text-sm font-medium text-custom-text-200"
>
<div className="flex items-center gap-2">
{group.icon}
<span>{group.name}</span>
</div>
<span className="text-xs text-custom-text-300">{itemCount}</span>
</button>
);
}

View file

@ -6,7 +6,9 @@ import { useGroupDropTarget } from "../hooks/use-group-drop-target";
import { GroupHeader } from "./group-header";
import { BaseKanbanItem } from "./item";
export const BaseKanbanGroup = observer(<T extends IBaseLayoutsKanbanItem>(props: IBaseLayoutsKanbanGroupProps<T>) => {
export const BaseKanbanGroup = observer(function BaseKanbanGroup<T extends IBaseLayoutsKanbanItem>(
props: IBaseLayoutsKanbanGroupProps<T>
) {
const {
group,
itemIds,

View file

@ -4,7 +4,9 @@ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-d
import { observer } from "mobx-react";
import type { IBaseLayoutsKanbanItem, IBaseLayoutsKanbanItemProps } from "@plane/types";
export const BaseKanbanItem = observer(<T extends IBaseLayoutsKanbanItem>(props: IBaseLayoutsKanbanItemProps<T>) => {
export const BaseKanbanItem = observer(function BaseKanbanItem<T extends IBaseLayoutsKanbanItem>(
props: IBaseLayoutsKanbanItemProps<T>
) {
const { item, groupId, renderItem, enableDragDrop, canDrag } = props;
const itemRef = useRef<HTMLDivElement | null>(null);

View file

@ -6,7 +6,9 @@ import { cn } from "@plane/utils";
import { useLayoutState } from "../hooks/use-layout-state";
import { BaseKanbanGroup } from "./group";
export const BaseKanbanLayout = observer(<T extends IBaseLayoutsKanbanItem>(props: IBaseLayoutsKanbanProps<T>) => {
export const BaseKanbanLayout = observer(function BaseKanbanLayout<T extends IBaseLayoutsKanbanItem>(
props: IBaseLayoutsKanbanProps<T>
) {
const {
items,
groups,

View file

@ -12,7 +12,7 @@ type Props = {
selectedLayout: TBaseLayoutType;
};
export const LayoutSwitcher: React.FC<Props> = (props) => {
export function LayoutSwitcher(props: Props) {
const { layouts, onChange, selectedLayout } = props;
const { isMobile } = usePlatformOS();
@ -47,4 +47,4 @@ export const LayoutSwitcher: React.FC<Props> = (props) => {
})}
</div>
);
};
}

View file

@ -1,12 +1,14 @@
import type { IGroupHeaderProps } from "@plane/types";
export const GroupHeader = ({ group, itemCount, onToggleGroup }: IGroupHeaderProps) => (
<button
onClick={() => onToggleGroup(group.id)}
className="flex w-full items-center gap-2 py-2 text-sm font-medium text-custom-text-200"
>
{group.icon}
<span>{group.name}</span>
<span className="text-xs text-custom-text-300">{itemCount}</span>
</button>
);
export function GroupHeader({ group, itemCount, onToggleGroup }: IGroupHeaderProps) {
return (
<button
onClick={() => onToggleGroup(group.id)}
className="flex w-full items-center gap-2 py-2 text-sm font-medium text-custom-text-200"
>
{group.icon}
<span>{group.name}</span>
<span className="text-xs text-custom-text-300">{itemCount}</span>
</button>
);
}

View file

@ -6,7 +6,9 @@ import { useGroupDropTarget } from "../hooks/use-group-drop-target";
import { GroupHeader } from "./group-header";
import { BaseListItem } from "./item";
export const BaseListGroup = observer(<T extends IBaseLayoutsListItem>(props: IBaseLayoutsListGroupProps<T>) => {
export const BaseListGroup = observer(function BaseListGroup<T extends IBaseLayoutsListItem>(
props: IBaseLayoutsListGroupProps<T>
) {
const {
group,
itemIds,

View file

@ -4,7 +4,9 @@ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-d
import { observer } from "mobx-react";
import type { IBaseLayoutsListItem, IBaseLayoutsListItemProps } from "@plane/types";
export const BaseListItem = observer(<T extends IBaseLayoutsListItem>(props: IBaseLayoutsListItemProps<T>) => {
export const BaseListItem = observer(function BaseListItem<T extends IBaseLayoutsListItem>(
props: IBaseLayoutsListItemProps<T>
) {
const { item, groupId, renderItem, enableDragDrop, canDrag, isLast: _isLast, index: _index } = props;
const itemRef = useRef<HTMLDivElement | null>(null);

View file

@ -6,7 +6,9 @@ import { cn } from "@plane/ui";
import { useLayoutState } from "../hooks/use-layout-state";
import { BaseListGroup } from "./group";
export const BaseListLayout = observer(<T extends IBaseLayoutsListItem>(props: IBaseLayoutsListProps<T>) => {
export const BaseListLayout = observer(function BaseListLayout<T extends IBaseLayoutsListItem>(
props: IBaseLayoutsListProps<T>
) {
const {
items,
groupedItemIds,

View file

@ -8,7 +8,7 @@ interface GenericLayoutLoaderProps {
customLoaders?: Partial<Record<TBaseLayoutType, React.ComponentType>>;
}
export const GenericLayoutLoader = ({ layout, customLoaders }: GenericLayoutLoaderProps) => {
export function GenericLayoutLoader({ layout, customLoaders }: GenericLayoutLoaderProps) {
const CustomLoader = customLoaders?.[layout];
if (CustomLoader) return <CustomLoader />;
@ -21,4 +21,4 @@ export const GenericLayoutLoader = ({ layout, customLoaders }: GenericLayoutLoad
console.warn(`Unknown layout: ${layout}`);
return null;
}
};
}

View file

@ -24,7 +24,7 @@ type Props = {
workspaceSlug: string;
};
export const CommentCardDisplay: React.FC<Props> = observer((props) => {
export const CommentCardDisplay = observer(function CommentCardDisplay(props: Props) {
const {
activityOperations,
comment,

View file

@ -21,7 +21,7 @@ type Props = {
workspaceSlug: string;
};
export const CommentCardEditForm: React.FC<Props> = observer((props) => {
export const CommentCardEditForm = observer(function CommentCardEditForm(props: Props) {
const {
activityOperations,
comment,

View file

@ -24,7 +24,7 @@ type TCommentCard = {
projectId?: string;
};
export const CommentCard: FC<TCommentCard> = observer((props) => {
export const CommentCard = observer(function CommentCard(props: TCommentCard) {
const {
workspaceSlug,
comment,

View file

@ -26,7 +26,7 @@ type TCommentCreate = {
// services
const fileService = new FileService();
export const CommentCreate: FC<TCommentCreate> = observer((props) => {
export const CommentCreate = observer(function CommentCreate(props: TCommentCreate) {
const {
workspaceSlug,
entityId,

View file

@ -18,7 +18,7 @@ export type TProps = {
activityOperations: TCommentsOperations;
};
export const CommentReactions: FC<TProps> = observer((props) => {
export const CommentReactions = observer(function CommentReactions(props: TProps) {
const { comment, activityOperations, disabled = false } = props;
// state
const [isPickerOpen, setIsPickerOpen] = useState(false);

View file

@ -23,7 +23,7 @@ type TCommentsWrapper = {
showCopyLinkOption?: boolean;
};
export const CommentsWrapper: FC<TCommentsWrapper> = observer((props) => {
export const CommentsWrapper = observer(function CommentsWrapper(props: TCommentsWrapper) {
const {
entityId,
activityOperations,

View file

@ -22,7 +22,7 @@ type TCommentCard = {
showCopyLinkOption: boolean;
};
export const CommentQuickActions: FC<TCommentCard> = observer((props) => {
export const CommentQuickActions = observer(function CommentQuickActions(props: TCommentCard) {
const { activityOperations, comment, setEditMode, showAccessSpecifier, showCopyLinkOption } = props;
// store hooks
const { data: currentUser } = useUser();
@ -33,8 +33,8 @@ export const CommentQuickActions: FC<TCommentCard> = observer((props) => {
// translation
const { t } = useTranslation();
const MENU_ITEMS: TContextMenuItem[] = useMemo(
() => [
const MENU_ITEMS = useMemo(function MENU_ITEMS() {
return [
{
key: "edit",
action: setEditMode,
@ -72,9 +72,8 @@ export const CommentQuickActions: FC<TCommentCard> = observer((props) => {
icon: Trash2,
shouldRender: canDelete,
},
],
[activityOperations, canDelete, canEdit, comment, setEditMode, showAccessSpecifier, showCopyLinkOption]
);
];
});
return (
<CustomMenu ellipsis closeOnSelect>

View file

@ -18,7 +18,7 @@ type Props = {
};
// TODO: Remove label once i18n is done
export const AccessField = (props: Props) => {
export function AccessField(props: Props) {
const { onChange, value, accessSpecifiers, isMobile = false } = props;
const { t } = useTranslation();
@ -50,4 +50,4 @@ export const AccessField = (props: Props) => {
})}
</div>
);
};
}

View file

@ -21,7 +21,7 @@ type TActivityBlockComponent = {
customUserName?: string;
};
export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
export function ActivityBlockComponent(props: TActivityBlockComponent) {
const { icon, activity, ends, children, customUserName } = props;
// hooks
const { isMobile } = usePlatformOS();
@ -53,4 +53,4 @@ export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
</div>
</div>
);
};
}

View file

@ -13,7 +13,7 @@ type TActivityItem = {
ends?: "top" | "bottom" | undefined;
};
export const ActivityItem: FC<TActivityItem> = observer((props) => {
export const ActivityItem = observer(function ActivityItem(props: TActivityItem) {
const { activity, showProject = true, ends } = props;
if (!activity) return null;

View file

@ -12,7 +12,7 @@ type TUser = {
customUserName?: string;
};
export const User: FC<TUser> = observer((props) => {
export const User = observer(function User(props: TUser) {
const { activity, customUserName } = props;
// store hooks
const { getUserDetails } = useMember();

View file

@ -11,7 +11,7 @@ type Props = {
values: string[];
};
export const AppliedDateFilters: React.FC<Props> = observer((props) => {
export const AppliedDateFilters = observer(function AppliedDateFilters(props: Props) {
const { editable, handleRemove, values } = props;
const getDateLabel = (value: string): string => {

View file

@ -15,7 +15,7 @@ type Props = {
editable: boolean | undefined;
};
export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
export const AppliedMembersFilters = observer(function AppliedMembersFilters(props: Props) {
const { handleRemove, values, editable } = props;
// store hooks
const {

View file

@ -15,19 +15,25 @@ type Props = {
isLast?: boolean;
};
const IconWrapper = React.memo(({ icon }: { icon: React.ReactNode }) => (
<div className="flex size-4 items-center justify-center overflow-hidden !text-[1rem]">{icon}</div>
));
const IconWrapper = React.memo(function IconWrapper({ icon }: { icon: React.ReactNode }) {
return <div className="flex size-4 items-center justify-center overflow-hidden !text-[1rem]">{icon}</div>;
});
IconWrapper.displayName = "IconWrapper";
const LabelWrapper = React.memo(({ label }: { label: ReactNode }) => (
<div className="relative line-clamp-1 block max-w-[150px] overflow-hidden truncate">{label}</div>
));
const LabelWrapper = React.memo(function LabelWrapper({ label }: { label: ReactNode }) {
return <div className="relative line-clamp-1 block max-w-[150px] overflow-hidden truncate">{label}</div>;
});
LabelWrapper.displayName = "LabelWrapper";
const BreadcrumbContent = React.memo(({ icon, label }: { icon?: React.ReactNode; label?: ReactNode }) => {
const BreadcrumbContent = React.memo(function BreadcrumbContent({
icon,
label,
}: {
icon?: React.ReactNode;
label?: ReactNode;
}) {
if (!icon && !label) return null;
return (
@ -40,13 +46,16 @@ const BreadcrumbContent = React.memo(({ icon, label }: { icon?: React.ReactNode;
BreadcrumbContent.displayName = "BreadcrumbContent";
const ItemWrapper = React.memo(({ children, ...props }: React.ComponentProps<typeof Breadcrumbs.ItemWrapper>) => (
<Breadcrumbs.ItemWrapper {...props}>{children}</Breadcrumbs.ItemWrapper>
));
const ItemWrapper = React.memo(function ItemWrapper({
children,
...props
}: React.ComponentProps<typeof Breadcrumbs.ItemWrapper>) {
return <Breadcrumbs.ItemWrapper {...props}>{children}</Breadcrumbs.ItemWrapper>;
});
ItemWrapper.displayName = "ItemWrapper";
export const BreadcrumbLink: FC<Props> = observer((props) => {
export const BreadcrumbLink = observer(function BreadcrumbLink(props: Props) {
const { href, label, icon, disableTooltip = false, isLast = false } = props;
const { isMobile } = usePlatformOS();

View file

@ -9,7 +9,7 @@ type TCountChip = {
className?: string;
};
export const CountChip: FC<TCountChip> = (props) => {
export function CountChip(props: TCountChip) {
const { count, className = "" } = props;
return (
@ -22,4 +22,4 @@ export const CountChip: FC<TCountChip> = (props) => {
{count}
</div>
);
};
}

View file

@ -17,32 +17,27 @@ type Props = {
disabled?: boolean;
};
export const EmptyState: React.FC<Props> = ({
title,
description,
image,
primaryButton,
secondaryButton,
disabled = false,
}) => (
<div className={`flex h-full w-full items-center justify-center`}>
<div className="flex w-full flex-col items-center text-center">
<img src={image} className="w-52 sm:w-60 object-contain" alt={primaryButton?.text || "button image"} />
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
<div className="flex items-center gap-4">
{primaryButton && (
<Button
variant="primary"
prependIcon={primaryButton.icon}
onClick={primaryButton.onClick}
disabled={disabled}
>
{primaryButton.text}
</Button>
)}
{secondaryButton}
export function EmptyState({ title, description, image, primaryButton, secondaryButton, disabled = false }: Props) {
return (
<div className={`flex h-full w-full items-center justify-center`}>
<div className="flex w-full flex-col items-center text-center">
<img src={image} className="w-52 sm:w-60 object-contain" alt={primaryButton?.text || "button image"} />
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
<div className="flex items-center gap-4">
{primaryButton && (
<Button
variant="primary"
prependIcon={primaryButton.icon}
onClick={primaryButton.onClick}
disabled={disabled}
>
{primaryButton.text}
</Button>
)}
{secondaryButton}
</div>
</div>
</div>
</div>
);
);
}

View file

@ -13,7 +13,7 @@ type Props = {
searchQuery: string;
};
export const FilterCreatedDate: React.FC<Props> = observer((props) => {
export const FilterCreatedDate = observer(function FilterCreatedDate(props: Props) {
const { appliedFilters, handleUpdate, searchQuery } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);

View file

@ -20,7 +20,7 @@ type Props = {
searchQuery: string;
};
export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
export const FilterCreatedBy = observer(function FilterCreatedBy(props: Props) {
const { appliedFilters, handleUpdate, memberIds, searchQuery } = props;
// states
const [itemsToRender, setItemsToRender] = useState(5);

View file

@ -5,7 +5,7 @@ import { Lightbulb } from "lucide-react";
// images
import latestFeatures from "@/app/assets/onboarding/onboarding-pages.webp?url";
export const LatestFeatureBlock = () => {
export function LatestFeatureBlock() {
const { resolvedTheme } = useTheme();
return (
@ -36,4 +36,4 @@ export const LatestFeatureBlock = () => {
</div>
</>
);
};
}

View file

@ -3,7 +3,7 @@ import { useTheme } from "next-themes";
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 = () => {
export function LogoSpinner() {
const { resolvedTheme } = useTheme();
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerDark : LogoSpinnerLight;
@ -13,4 +13,4 @@ export const LogoSpinner = () => {
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11 object-contain" />
</div>
);
};
}

View file

@ -22,14 +22,7 @@ type Props = {
disabled?: boolean;
};
export const NewEmptyState: React.FC<Props> = ({
title,
description,
image,
primaryButton,
disabled = false,
comicBox,
}) => {
export function NewEmptyState({ title, description, image, primaryButton, disabled = false, comicBox }: Props) {
const [isHovered, setIsHovered] = useState(false);
const handleMouseEnter = () => {
@ -109,4 +102,4 @@ export const NewEmptyState: React.FC<Props> = ({
</div>
</div>
);
};
}

View file

@ -2,14 +2,16 @@ import { ArchiveIcon, Earth, Lock } from "lucide-react";
import { EPageAccess } from "@plane/constants";
import type { TPage } from "@plane/types";
export const PageAccessIcon = (page: TPage) => (
<div>
{page.archived_at ? (
<ArchiveIcon className="h-2.5 w-2.5 text-custom-text-300" />
) : page.access === EPageAccess.PUBLIC ? (
<Earth className="h-2.5 w-2.5 text-custom-text-300" />
) : (
<Lock className="h-2.5 w-2.5 text-custom-text-300" />
)}
</div>
);
export function PageAccessIcon(page: TPage) {
return (
<div>
{page.archived_at ? (
<ArchiveIcon className="h-2.5 w-2.5 text-custom-text-300" />
) : page.access === EPageAccess.PUBLIC ? (
<Earth className="h-2.5 w-2.5 text-custom-text-300" />
) : (
<Lock className="h-2.5 w-2.5 text-custom-text-300" />
)}
</div>
);
}

View file

@ -9,8 +9,8 @@ type TProIcon = {
className?: string;
};
export const ProIcon: FC<TProIcon> = (props) => {
export function ProIcon(props: TProIcon) {
const { className } = props;
return <Crown className={cn("inline-block h-3.5 w-3.5 text-amber-400", className)} />;
};
}

View file

@ -12,13 +12,7 @@ type TSwitcherIconProps = {
type?: "lucide" | "material";
};
export const SwitcherIcon: FC<TSwitcherIconProps> = ({
logo_props,
logo_url,
LabelIcon,
size = 12,
type = "lucide",
}) => {
export function SwitcherIcon({ logo_props, logo_url, LabelIcon, size = 12, type = "lucide" }: TSwitcherIconProps) {
if (logo_props?.in_use) {
return <Logo logo={logo_props} size={size} type={type} />;
}
@ -34,7 +28,7 @@ export const SwitcherIcon: FC<TSwitcherIconProps> = ({
);
}
return <LabelIcon height={size} width={size} />;
};
}
type TSwitcherLabelProps = {
logo_props?: TLogoProps;
@ -44,7 +38,7 @@ type TSwitcherLabelProps = {
type?: "lucide" | "material";
};
export const SwitcherLabel: FC<TSwitcherLabelProps> = (props) => {
export function SwitcherLabel(props: TSwitcherLabelProps) {
const { logo_props, name, LabelIcon, logo_url, type = "lucide" } = props;
return (
<div className="flex items-center gap-1 text-custom-text-200">
@ -52,4 +46,4 @@ export const SwitcherLabel: FC<TSwitcherLabelProps> = (props) => {
{truncateText(name ?? "", 40)}
</div>
);
};
}

View file

@ -1,5 +1,4 @@
"use client";
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
@ -37,7 +36,7 @@ import { useLabel } from "@/hooks/store/use-label";
import { usePlatformOS } from "@/hooks/use-platform-os";
// types
export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
export function IssueLink({ activity }: { activity: IIssueActivity }) {
// router params
const { workspaceSlug } = useParams();
const { isMobile } = usePlatformOS();
@ -73,9 +72,9 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
)}
</Tooltip>
);
};
}
const UserLink = ({ activity }: { activity: IIssueActivity }) => {
function UserLink({ activity }: { activity: IIssueActivity }) {
// router params
const { workspaceSlug } = useParams();
@ -91,9 +90,9 @@ const UserLink = ({ activity }: { activity: IIssueActivity }) => {
{activity.new_value && activity.new_value !== "" ? activity.new_value : activity.old_value}
</a>
);
};
}
const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) => {
const LabelPill = observer(function LabelPill({ labelId, workspaceSlug }: { labelId: string; workspaceSlug: string }) {
// store hooks
const { workspaceLabels, fetchWorkspaceLabels } = useLabel();
@ -751,16 +750,16 @@ const activityDetails: {
},
};
export const ActivityIcon = ({ activity }: { activity: IIssueActivity }) => (
<>{activityDetails[activity.field as keyof typeof activityDetails]?.icon}</>
);
export function ActivityIcon({ activity }: { activity: IIssueActivity }) {
return <>{activityDetails[activity.field as keyof typeof activityDetails]?.icon}</>;
}
type ActivityMessageProps = {
activity: IIssueActivity;
showIssue?: boolean;
};
export const ActivityMessage = ({ activity, showIssue = false }: ActivityMessageProps) => {
export function ActivityMessage({ activity, showIssue = false }: ActivityMessageProps) {
// router params
const { workspaceSlug } = useParams();
const activityField = activity.field ?? "issue";
@ -774,4 +773,4 @@ export const ActivityMessage = ({ activity, showIssue = false }: ActivityMessage
)}
</>
);
};
}

View file

@ -12,7 +12,7 @@ export interface AppHeaderProps {
mobileHeader?: ReactNode;
}
export const AppHeader = observer((props: AppHeaderProps) => {
export const AppHeader = observer(function AppHeader(props: AppHeaderProps) {
const { header, mobileHeader } = props;
return (

View file

@ -14,7 +14,7 @@ interface IContentOverflowWrapper {
customButton?: ReactNode;
}
export const ContentOverflowWrapper = observer((props: IContentOverflowWrapper) => {
export const ContentOverflowWrapper = observer(function ContentOverflowWrapper(props: IContentOverflowWrapper) {
const {
children,
maxHeight = 625,

View file

@ -9,8 +9,10 @@ export interface ContentWrapperProps {
children: ReactNode;
}
export const ContentWrapper = ({ className, children }: ContentWrapperProps) => (
<div className="h-full w-full overflow-hidden">
<div className={cn("relative h-full w-full overflow-x-hidden overflow-y-scroll", className)}>{children}</div>
</div>
);
export function ContentWrapper({ className, children }: ContentWrapperProps) {
return (
<div className="h-full w-full overflow-hidden">
<div className={cn("relative h-full w-full overflow-x-hidden overflow-y-scroll", className)}>{children}</div>
</div>
);
}

View file

@ -12,7 +12,7 @@ type Props = {
version: TDescriptionVersion;
};
export const DescriptionVersionsDropdownItem: React.FC<Props> = observer((props) => {
export const DescriptionVersionsDropdownItem = observer(function DescriptionVersionsDropdownItem(props: Props) {
const { onClick, version } = props;
// store hooks
const { getUserDetails } = useMember();

View file

@ -18,7 +18,7 @@ type Props = {
versions: TDescriptionVersion[] | undefined;
};
export const DescriptionVersionsDropdown: React.FC<Props> = observer((props) => {
export const DescriptionVersionsDropdown = observer(function DescriptionVersionsDropdown(props: Props) {
const { disabled, entityInformation, onVersionClick, versions } = props;
// store hooks
const { getUserDetails } = useMember();

View file

@ -31,7 +31,7 @@ type Props = {
workspaceSlug: string;
};
export const DescriptionVersionsModal: React.FC<Props> = observer((props) => {
export const DescriptionVersionsModal = observer(function DescriptionVersionsModal(props: Props) {
const {
activeVersionDescription,
activeVersionDetails,

Some files were not shown because too many files have changed in this diff Show more