[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
1771 changed files with 17003 additions and 13856 deletions

View File

@@ -0,0 +1,48 @@
---
description: Guidelines for bash commands and tooling in the monorepo
applyTo: "**/*.sh"
---
# Bash & Tooling Instructions
This document outlines the standard tools and commands used in this monorepo.
## Package Manager
We use **pnpm** for package management.
- **Do not use `npm` or `yarn`.**
- Lockfile: `pnpm-lock.yaml`
- Workspace configuration: `pnpm-workspace.yaml`
### Common Commands
- Install dependencies: `pnpm install`
- Run a script in a specific package: `pnpm --filter <package_name> run <script>`
- Run a script in all packages: `pnpm -r run <script>`
## Monorepo Tooling
We use **Turbo** for build system orchestration.
- Configuration: `turbo.json`
## Project Structure
- `apps/`: Contains application services (admin, api, live, proxy, space, web).
- `packages/`: Contains shared packages and libraries.
- `deployments/`: Deployment configurations.
## Running Tests
- To run tests in a specific package (e.g., codemods):
```bash
cd packages/codemods
pnpm run test
```
- Or from root:
```bash
pnpm --filter @plane/codemods run test
```
## Docker
- Local development uses `docker-compose-local.yml`.
- Production/Staging uses `docker-compose.yml`.

View File

@@ -0,0 +1,129 @@
---
description: Guidelines for using modern TypeScript features (v5.0-v5.8)
applyTo: "**/*.{ts,tsx,mts,cts}"
---
# TypeScript Coding Guidelines & Modern Features (v5.0 - v5.8)
When writing TypeScript code, prioritize using modern features and best practices introduced in recent versions (up to 5.8).
## Global Themes Across 5.x
1. **Standard decorators are here; legacy decorators are legacy.**
New TC39-compliant decorators landed in 5.0 and were extended in 5.2 (metadata). Old `experimentalDecorators`-style behavior is still supported but should be treated as legacy.
2. **Type system is more precise and less noisy.**
Major work went into narrowing, control flow analysis, error messages, and new helpers like `NoInfer`, inferred predicates, and better `undefined`/`never`/uninitialized checks.
3. **Module / runtime interop has been modernized.**
Options like `--moduleResolution bundler`, `--module nodenext`/`node18`, `--rewriteRelativeImportExtensions`, `--erasableSyntaxOnly`, and `--verbatimModuleSyntax` are about playing nicely with ESM, Node 18+/22+, direct TypeScript execution, and bundlers.
4. **The standard library keeps tracking modern JS.**
Support for new ES features (iterator helpers, `Object.groupBy`/`Map.groupBy`, new Set/ES2024 APIs) shows up as type declarations and sometimes extra checks (regex syntax checking, etc.).
When generating or refactoring code, prefer these newer idioms, and avoid patterns that conflict with updated checks.
## Modern Features to Utilize
### Type System & Inference
- **`const` Type Parameters (5.0)**: Use `const` type parameters for more precise literal inference.
```typescript
declare function names<const T extends string[]>(...names: T): void;
```
- **`@satisfies` Operator (5.0)**: Use `satisfies` to validate types without widening them.
- **Inferred Type Predicates (5.5)**: Allow TypeScript to infer type predicates for functions that filter arrays or check types, reducing the need for explicit `is` return types.
- **`NoInfer` Utility (5.4)**: Use `NoInfer<T>` to block inference for specific type arguments when you want them to be determined by other arguments.
- **Narrowing**:
- **Switch(true) (5.3)**: Utilize narrowing in `switch(true)` blocks.
- **Boolean Comparisons (5.3)**: Rely on narrowing from direct boolean comparisons.
- **Closures (5.4)**: Trust preserved narrowing in closures when variables aren't modified after the check.
- **Constant Indexed Access (5.5)**: Use constant indices to narrow object/array properties.
### Syntax & Control Flow
- **Decorators (5.0)**: Use standard ECMAScript decorators (Stage 3).
- **`using` Declarations (5.2)**: Use `using` for explicit resource management (Disposable pattern) instead of manual cleanup.
```typescript
using resource = new Resource();
```
- **Import Attributes (5.3/5.8)**: Use `with { type: "json" }` for import attributes. Avoid the deprecated `assert` syntax.
- **`switch` Exhaustiveness**: Rely on TypeScript's exhaustiveness checking in switch statements.
### Modules & Imports
- **`verbatimModuleSyntax` (5.0)**: Respect this flag by using `import type` explicitly when importing types to ensure they are erased during compilation.
- **Type-Only Imports with Extensions (5.2)**: You can use `.ts`, `.mts`, `.cts` extensions in `import type` statements.
- **`resolution-mode` (5.3)**: Use `import type { Type } from "mod" with { "resolution-mode": "import" }` if needed for specific module resolution contexts.
- **JSDoc `@import` (5.5)**: Use `@import` tags in JSDoc for cleaner type imports in JS files if working in a mixed codebase.
### Standard Library & Built-ins
- **Iterator Helpers (5.6)**: Use new iterator methods (map, filter, etc.) if targeting modern environments.
- **Set Methods (5.5)**: Utilize new `Set` methods like `union`, `intersection`, etc., when available.
- **`Object.groupBy` / `Map.groupBy` (5.4)**: Use these standard methods for grouping instead of external libraries like Lodash when appropriate.
- **`Promise.withResolvers` (5.7)**: Use `Promise.withResolvers()` for creating promises with exposed resolve/reject functions.
### Configuration & Tooling
- **`--moduleResolution bundler` (5.0)**: Assume this resolution strategy for modern web projects (Vite, Next.js, etc.).
- **`--erasableSyntaxOnly` (5.8)**: Be aware of this flag; avoid TypeScript-specific syntax that cannot be simply erased (like `enum`s or `namespaces`) if the project aims for maximum compatibility with tools like Node.js's `--strip-types`. Prefer `const` objects or unions over `enum`s if requested.
## Specific Coding Patterns
### Arrays & Collections
- Use **Copying Array Methods (5.2)** (`toSorted`, `toSpliced`, `with`) for immutable array operations.
- **TypedArrays (5.7)**: Be aware that TypedArrays are now generic over `ArrayBufferLike`.
### Classes
- **Parameter Decorators (5.0/5.2)**: Use modern standard decorators.
- **`super` Property Access (5.3)**: Avoid accessing instance fields via `super`.
### Error Handling
- **Checks for Never-Initialized Variables (5.7)**: Ensure variables are initialized before use to avoid new errors.
## Deprecations to Avoid
- Avoid `import ... assert` (use `with`).
- Avoid implicit `any` returns in `undefined`-returning functions (though 5.1 makes this easier, explicit is better).
- Avoid `enum`s if the project prefers erasable syntax (5.8).
## Version-Specific Highlights
### TypeScript 5.0
- **Decorators**: Use standard decorators unless `experimentalDecorators` is explicitly enabled.
- **`const` Type Parameters**: Use for literal inference.
- **Enums**: All enums are union enums.
- **Modules**: `--moduleResolution bundler` and `--verbatimModuleSyntax` are key for modern bundlers.
### TypeScript 5.1
- **Returns**: `undefined`-returning functions don't need explicit returns.
- **Getters/Setters**: Can have unrelated types with explicit annotations.
### TypeScript 5.2
- **Resource Management**: `using` declarations for `Symbol.dispose`.
- **Decorator Metadata**: Use `context.metadata` for design-time metadata.
### TypeScript 5.3
- **Import Attributes**: Use `with { type: "json" }`.
- **Switch(true)**: Narrowing works in `switch(true)`.
### TypeScript 5.4
- **Closures**: Narrowing preserved in closures if last assignment is before creation.
- **`NoInfer`**: Block inference for specific arguments.
- **Grouping**: `Object.groupBy` / `Map.groupBy`.
### TypeScript 5.5
- **Inferred Predicates**: Functions checking types often don't need explicit `is` return types.
- **Constant Index Access**: Better narrowing for constant keys.
- **Regex**: Syntax checking for regex literals.
### TypeScript 5.6
- **Truthiness Checks**: Errors on always-truthy/falsy conditions (e.g., `if (/regex/)`).
- **Iterator Helpers**: `.map`, `.filter` on iterators.
### TypeScript 5.7
- **Uninitialized Variables**: Stricter checks for never-initialized variables.
- **Relative Imports**: `--rewriteRelativeImportExtensions` for `.ts` imports in output.
- **ES2024**: Support for `Promise.withResolvers`, `Atomics.waitAsync`.
### TypeScript 5.8
- **Return Checks**: Granular checks for conditional returns.
- **Node Modules**: `--module node18` stable; `require()` of ESM allowed in `nodenext`.
- **Erasable Syntax**: `--erasableSyntaxOnly` forbids enums, namespaces, etc.
When generating code, always prefer the most modern, standard, and type-safe approach available in TypeScript 5.8.

View File

@@ -17,7 +17,7 @@ type IInstanceAIForm = {
type AIFormValues = Record<TInstanceAIConfigurationKeys, string>;
export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => {
export function InstanceAIForm(props: IInstanceAIForm) {
const { config } = props;
// store
const { updateInstanceConfigurations } = useInstance();
@@ -133,4 +133,4 @@ export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => {
</div>
</div>
);
};
}

View File

@@ -9,7 +9,7 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceAIForm } from "./form";
const InstanceAIPage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceAIPage = observer(function InstanceAIPage(_props: Route.ComponentProps) {
// store
const { fetchInstanceConfigurations, formattedConfig } = useInstance();

View File

@@ -27,7 +27,7 @@ type Props = {
type GiteaConfigFormValues = Record<TInstanceGiteaAuthenticationConfigurationKeys, string>;
export const InstanceGiteaConfigForm: FC<Props> = (props) => {
export function InstanceGiteaConfigForm(props: Props) {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
@@ -207,4 +207,4 @@ export const InstanceGiteaConfigForm: FC<Props> = (props) => {
</div>
</>
);
};
}

View File

@@ -15,7 +15,7 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceGiteaConfigForm } from "./form";
const InstanceGiteaAuthenticationPage = observer(() => {
const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthenticationPage() {
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
// state

View File

@@ -28,7 +28,7 @@ type Props = {
type GithubConfigFormValues = Record<TInstanceGithubAuthenticationConfigurationKeys, string>;
export const InstanceGithubConfigForm: React.FC<Props> = (props) => {
export function InstanceGithubConfigForm(props: Props) {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
@@ -245,4 +245,4 @@ export const InstanceGithubConfigForm: React.FC<Props> = (props) => {
</div>
</>
);
};
}

View File

@@ -19,7 +19,9 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceGithubConfigForm } from "./form";
const InstanceGithubAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthenticationPage(
_props: Route.ComponentProps
) {
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
// state

View File

@@ -24,7 +24,7 @@ type Props = {
type GitlabConfigFormValues = Record<TInstanceGitlabAuthenticationConfigurationKeys, string>;
export const InstanceGitlabConfigForm: React.FC<Props> = (props) => {
export function InstanceGitlabConfigForm(props: Props) {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
@@ -208,4 +208,4 @@ export const InstanceGitlabConfigForm: React.FC<Props> = (props) => {
</div>
</>
);
};
}

View File

@@ -15,7 +15,9 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceGitlabConfigForm } from "./form";
const InstanceGitlabAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthenticationPage(
_props: Route.ComponentProps
) {
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
// state

View File

@@ -27,7 +27,7 @@ type Props = {
type GoogleConfigFormValues = Record<TInstanceGoogleAuthenticationConfigurationKeys, string>;
export const InstanceGoogleConfigForm: React.FC<Props> = (props) => {
export function InstanceGoogleConfigForm(props: Props) {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
@@ -232,4 +232,4 @@ export const InstanceGoogleConfigForm: React.FC<Props> = (props) => {
</div>
</>
);
};
}

View File

@@ -15,7 +15,9 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceGoogleConfigForm } from "./form";
const InstanceGoogleAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthenticationPage(
_props: Route.ComponentProps
) {
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
// state

View File

@@ -14,7 +14,7 @@ import { useInstance } from "@/hooks/store";
import { AuthenticationModes } from "@/plane-admin/components/authentication";
import type { Route } from "./+types/page";
const InstanceAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(_props: Route.ComponentProps) {
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();

View File

@@ -30,7 +30,7 @@ const EMAIL_SECURITY_OPTIONS: { [key in TEmailSecurityKeys]: string } = {
NONE: "No email security",
};
export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => {
export function InstanceEmailForm(props: IInstanceEmailForm) {
const { config } = props;
// states
const [isSendTestEmailModalOpen, setIsSendTestEmailModalOpen] = useState(false);
@@ -224,4 +224,4 @@ export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => {
</div>
</div>
);
};
}

View File

@@ -11,7 +11,7 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceEmailForm } from "./email-config-form";
const InstanceEmailPage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceEmailPage = observer(function InstanceEmailPage(_props: Route.ComponentProps) {
// store
const { fetchInstanceConfigurations, formattedConfig, disableEmail } = useInstance();

View File

@@ -19,7 +19,7 @@ enum ESendEmailSteps {
const instanceService = new InstanceService();
export const SendTestEmailModal: React.FC<Props> = (props) => {
export function SendTestEmailModal(props: Props) {
const { isOpen, handleClose } = props;
// state
@@ -133,4 +133,4 @@ export const SendTestEmailModal: React.FC<Props> = (props) => {
</Dialog>
</Transition.Root>
);
};
}

View File

@@ -19,7 +19,7 @@ export interface IGeneralConfigurationForm {
instanceAdmins: IInstanceAdmin[];
}
export const GeneralConfigurationForm: React.FC<IGeneralConfigurationForm> = observer((props) => {
export const GeneralConfigurationForm = observer(function GeneralConfigurationForm(props: IGeneralConfigurationForm) {
const { instance, instanceAdmins } = props;
// hooks
const { instanceConfigurations, updateInstanceInfo, updateInstanceConfigurations } = useInstance();

View File

@@ -13,7 +13,7 @@ type TIntercomConfig = {
isTelemetryEnabled: boolean;
};
export const IntercomConfig: React.FC<TIntercomConfig> = observer((props) => {
export const IntercomConfig = observer(function IntercomConfig(props: TIntercomConfig) {
const { isTelemetryEnabled } = props;
// hooks
const { instanceConfigurations, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance();

View File

@@ -10,7 +10,7 @@ import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
// hooks
import { useTheme } from "@/hooks/store";
export const HamburgerToggle = observer(() => {
export const HamburgerToggle = observer(function HamburgerToggle() {
const { isSidebarCollapsed, toggleSidebar } = useTheme();
return (
<div
@@ -22,7 +22,7 @@ export const HamburgerToggle = observer(() => {
);
});
export const AdminHeader = observer(() => {
export const AdminHeader = observer(function AdminHeader() {
const pathName = usePathname();
const getHeaderTitle = (pathName: string) => {

View File

@@ -14,7 +14,7 @@ type IInstanceImageConfigForm = {
type ImageConfigFormValues = Record<TInstanceImageConfigurationKeys, string>;
export const InstanceImageConfigForm: React.FC<IInstanceImageConfigForm> = (props) => {
export function InstanceImageConfigForm(props: IInstanceImageConfigForm) {
const { config } = props;
// store hooks
const { updateInstanceConfigurations } = useInstance();
@@ -77,4 +77,4 @@ export const InstanceImageConfigForm: React.FC<IInstanceImageConfigForm> = (prop
</div>
</div>
);
};
}

View File

@@ -9,7 +9,7 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceImageConfigForm } from "./form";
const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => {
const InstanceImagePage = observer(function InstanceImagePage(_props: Route.ComponentProps) {
// store
const { formattedConfig, fetchInstanceConfigurations } = useInstance();

View File

@@ -1,5 +1,4 @@
"use client";
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/navigation";
@@ -14,7 +13,7 @@ import type { Route } from "./+types/layout";
import { AdminHeader } from "./header";
import { AdminSidebar } from "./sidebar";
const AdminLayout: React.FC<Route.ComponentProps> = () => {
function AdminLayout(_props: Route.ComponentProps) {
// router
const { replace } = useRouter();
// store hooks
@@ -48,6 +47,6 @@ const AdminLayout: React.FC<Route.ComponentProps> = () => {
}
return <></>;
};
}
export default observer(AdminLayout);

View File

@@ -16,7 +16,7 @@ import { useTheme, useUser } from "@/hooks/store";
// service initialization
const authService = new AuthService();
export const AdminSidebarDropdown = observer(() => {
export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
// store hooks
const { isSidebarCollapsed } = useTheme();
const { currentUser, signOut } = useUser();

View File

@@ -34,7 +34,7 @@ const helpOptions = [
},
];
export const AdminSidebarHelpSection: React.FC = observer(() => {
export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection() {
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// store

View File

@@ -50,7 +50,7 @@ const INSTANCE_ADMIN_LINKS = [
},
];
export const AdminSidebarMenu = observer(() => {
export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
// store hooks
const { isSidebarCollapsed, toggleSidebar } = useTheme();
// router

View File

@@ -11,7 +11,7 @@ import { AdminSidebarDropdown } from "./sidebar-dropdown";
import { AdminSidebarHelpSection } from "./sidebar-help-section";
import { AdminSidebarMenu } from "./sidebar-menu";
export const AdminSidebar = observer(() => {
export const AdminSidebar = observer(function AdminSidebar() {
// store
const { isSidebarCollapsed, toggleSidebar } = useTheme();

View File

@@ -15,7 +15,7 @@ import { useWorkspace } from "@/hooks/store";
const instanceWorkspaceService = new InstanceWorkspaceService();
export const WorkspaceCreateForm = () => {
export function WorkspaceCreateForm() {
// router
const router = useRouter();
// states
@@ -209,4 +209,4 @@ export const WorkspaceCreateForm = () => {
</div>
</div>
);
};
}

View File

@@ -5,19 +5,21 @@ import { observer } from "mobx-react";
import type { Route } from "./+types/page";
import { WorkspaceCreateForm } from "./form";
const WorkspaceCreatePage = observer<React.FC<Route.ComponentProps>>(() => (
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">Create a new workspace on this instance.</div>
<div className="text-sm font-normal text-custom-text-300">
You will need to invite users from Workspace Settings after you create this workspace.
const WorkspaceCreatePage = observer(function WorkspaceCreatePage(_props: Route.ComponentProps) {
return (
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">Create a new workspace on this instance.</div>
<div className="text-sm font-normal text-custom-text-300">
You will need to invite users from Workspace Settings after you create this workspace.
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
<WorkspaceCreateForm />
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
<WorkspaceCreateForm />
</div>
</div>
));
);
});
export const meta: Route.MetaFunction = () => [{ title: "Create Workspace - God Mode" }];

View File

@@ -18,7 +18,7 @@ import { WorkspaceListItem } from "@/components/workspace/list-item";
import { useInstance, useWorkspace } from "@/hooks/store";
import type { Route } from "./+types/page";
const WorkspaceManagementPage = observer<React.FC<Route.ComponentProps>>(() => {
const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props: Route.ComponentProps) {
// states
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
// store

View File

@@ -9,7 +9,7 @@ type TAuthBanner = {
handleBannerData?: (bannerData: TAdminAuthErrorInfo | undefined) => void;
};
export const AuthBanner: React.FC<TAuthBanner> = (props) => {
export function AuthBanner(props: TAuthBanner) {
const { bannerData, handleBannerData } = props;
if (!bannerData) return <></>;
@@ -27,4 +27,4 @@ export const AuthBanner: React.FC<TAuthBanner> = (props) => {
</div>
</div>
);
};
}

View File

@@ -3,10 +3,12 @@
import Link from "next/link";
import { PlaneLockup } from "@plane/propel/icons";
export const AuthHeader = () => (
<div className="flex items-center justify-between gap-6 w-full flex-shrink-0 sticky top-0">
<Link href="/">
<PlaneLockup height={20} width={95} className="text-custom-text-100" />
</Link>
</div>
);
export function AuthHeader() {
return (
<div className="flex items-center justify-between gap-6 w-full flex-shrink-0 sticky top-0">
<Link href="/">
<PlaneLockup height={20} width={95} className="text-custom-text-100" />
</Link>
</div>
);
}

View File

@@ -1,5 +1,4 @@
"use client";
import { observer } from "mobx-react";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
@@ -11,7 +10,7 @@ import { useInstance } from "@/hooks/store";
import type { Route } from "./+types/page";
import { InstanceSignInForm } from "./sign-in-form";
const HomePage = () => {
function HomePage() {
// store hooks
const { instance, error } = useInstance();
@@ -36,7 +35,7 @@ const HomePage = () => {
// if instance is fetched and setup is done, show sign in form
return <InstanceSignInForm />;
};
}
export default observer(HomePage);

View File

@@ -45,7 +45,7 @@ const defaultFromData: TFormData = {
password: "",
};
export const InstanceSignInForm: React.FC = () => {
export function InstanceSignInForm() {
// search params
const searchParams = useSearchParams();
const emailParam = searchParams.get("email") || undefined;
@@ -192,4 +192,4 @@ export const InstanceSignInForm: React.FC = () => {
</div>
</>
);
};
}

View File

@@ -3,7 +3,7 @@ import useSWR from "swr";
// hooks
import { useInstance } from "@/hooks/store";
export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => {
export const InstanceProvider = observer(function InstanceProvider(props: React.PropsWithChildren) {
const { children } = props;
// store hooks
const { fetchInstanceInfo } = useInstance();

View File

@@ -28,7 +28,7 @@ export type StoreProviderProps = {
initialState?: any;
};
export const StoreProvider = ({ children, initialState = {} }: StoreProviderProps) => {
export function StoreProvider({ children, initialState = {} }: StoreProviderProps) {
const store = initializeStore(initialState);
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};
}

View File

@@ -4,7 +4,7 @@ import { useTheme } from "next-themes";
import { Toast } from "@plane/propel/toast";
import { resolveGeneralTheme } from "@plane/utils";
export const ToastWithTheme = () => {
export function ToastWithTheme() {
const { resolvedTheme } = useTheme();
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />;
};
}

View File

@@ -6,7 +6,7 @@ import useSWR from "swr";
// hooks
import { useInstance, useTheme, useUser } from "@/hooks/store";
export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => {
export const UserProvider = observer(function UserProvider({ children }: React.PropsWithChildren) {
// hooks
const { isSidebarCollapsed, toggleSidebar } = useTheme();
const { currentUser, fetchCurrentUser } = useUser();

View File

@@ -1,5 +1,4 @@
"use client";
import React from "react";
// Minimal shim so code using next/image compiles under React Router + Vite
@@ -9,6 +8,8 @@ type NextImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
src: string;
};
const Image: React.FC<NextImageProps> = ({ src, alt = "", ...rest }) => <img src={src} alt={alt} {...rest} />;
function Image({ src, alt = "", ...rest }: NextImageProps) {
return <img src={src} alt={alt} {...rest} />;
}
export default Image;

View File

@@ -1,5 +1,4 @@
"use client";
import React from "react";
import { Link as RRLink } from "react-router";
import { ensureTrailingSlash } from "./helper";
@@ -12,13 +11,8 @@ type NextLinkProps = React.ComponentProps<"a"> & {
shallow?: boolean; // next.js prop, ignored
};
const Link: React.FC<NextLinkProps> = ({
href,
replace,
prefetch: _prefetch,
scroll: _scroll,
shallow: _shallow,
...rest
}) => <RRLink to={ensureTrailingSlash(href)} replace={replace} {...rest} />;
function Link({ href, replace, prefetch: _prefetch, scroll: _scroll, shallow: _shallow, ...rest }: NextLinkProps) {
return <RRLink to={ensureTrailingSlash(href)} replace={replace} {...rest} />;
}
export default Link;

View File

@@ -5,30 +5,32 @@ import { Button } from "@plane/propel/button";
// images
import Image404 from "@/app/assets/images/404.svg?url";
const PageNotFound = () => (
<div className={`h-screen w-full overflow-hidden bg-custom-background-100`}>
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="relative mx-auto h-60 w-60 lg:h-80 lg:w-80">
<img src={Image404} alt="404 - Page not found" className="h-full w-full object-contain" />
function PageNotFound() {
return (
<div className={`h-screen w-full overflow-hidden bg-custom-background-100`}>
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="relative mx-auto h-60 w-60 lg:h-80 lg:w-80">
<img src={Image404} alt="404 - Page not found" className="h-full w-full object-contain" />
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">Oops! Something went wrong.</h3>
<p className="text-sm text-custom-text-200">
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
temporarily unavailable.
</p>
</div>
<Link to="/general/">
<span className="flex justify-center py-4">
<Button variant="neutral-primary" size="md">
Go to general settings
</Button>
</span>
</Link>
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">Oops! Something went wrong.</h3>
<p className="text-sm text-custom-text-200">
Sorry, the page you are looking for cannot be found. It may have been removed, had its name changed, or is
temporarily unavailable.
</p>
</div>
<Link to="/general/">
<span className="flex justify-center py-4">
<Button variant="neutral-primary" size="md">
Go to general settings
</Button>
</span>
</Link>
</div>
</div>
</div>
);
);
}
export default PageNotFound;

View File

@@ -106,7 +106,7 @@ export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) =>
},
];
export const AuthenticationModes = observer<React.FC<TAuthenticationModeProps>>((props) => {
export const AuthenticationModes = observer(function AuthenticationModes(props: TAuthenticationModeProps) {
const { disabled, updateConfig } = props;
// next-themes
const { resolvedTheme } = useTheme();

View File

@@ -7,9 +7,15 @@ import { SquareArrowOutUpRight } from "lucide-react";
import { getButtonStyling } from "@plane/propel/button";
import { cn } from "@plane/utils";
export const UpgradeButton: React.FC = () => (
<a href="https://plane.so/pricing?mode=self-hosted" target="_blank" className={cn(getButtonStyling("primary", "sm"))}>
Upgrade
<SquareArrowOutUpRight className="h-3.5 w-3.5 p-0.5" />
</a>
);
export function UpgradeButton() {
return (
<a
href="https://plane.so/pricing?mode=self-hosted"
target="_blank"
className={cn(getButtonStyling("primary", "sm"))}
>
Upgrade
<SquareArrowOutUpRight className="h-3.5 w-3.5 p-0.5" />
</a>
);
}

View File

@@ -13,7 +13,7 @@ type Props = {
unavailable?: boolean;
};
export const AuthenticationMethodCard: React.FC<Props> = (props) => {
export function AuthenticationMethodCard(props: Props) {
const { name, description, icon, config, disabled = false, withBorder = true, unavailable = false } = props;
return (
@@ -52,4 +52,4 @@ export const AuthenticationMethodCard: React.FC<Props> = (props) => {
<div className={`shrink-0 ${disabled && "opacity-70"}`}>{config}</div>
</div>
);
};
}

View File

@@ -14,7 +14,7 @@ type Props = {
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export const EmailCodesConfiguration: React.FC<Props> = observer((props) => {
export const EmailCodesConfiguration = observer(function EmailCodesConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();

View File

@@ -17,7 +17,7 @@ type Props = {
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export const GiteaConfiguration: React.FC<Props> = observer((props) => {
export const GiteaConfiguration = observer(function GiteaConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();

View File

@@ -18,7 +18,7 @@ type Props = {
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export const GithubConfiguration: React.FC<Props> = observer((props) => {
export const GithubConfiguration = observer(function GithubConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();

View File

@@ -17,7 +17,7 @@ type Props = {
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export const GitlabConfiguration: React.FC<Props> = observer((props) => {
export const GitlabConfiguration = observer(function GitlabConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();

View File

@@ -17,7 +17,7 @@ type Props = {
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export const GoogleConfiguration: React.FC<Props> = observer((props) => {
export const GoogleConfiguration = observer(function GoogleConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();

View File

@@ -14,7 +14,7 @@ type Props = {
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export const PasswordLoginConfiguration: React.FC<Props> = observer((props) => {
export const PasswordLoginConfiguration = observer(function PasswordLoginConfiguration(props: Props) {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();

View File

@@ -6,7 +6,7 @@ type TBanner = {
message: string;
};
export const Banner: FC<TBanner> = (props) => {
export function Banner(props: TBanner) {
const { type, message } = props;
return (
@@ -29,4 +29,4 @@ export const Banner: FC<TBanner> = (props) => {
</div>
</div>
);
};
}

View File

@@ -9,7 +9,7 @@ type Props = {
icon?: React.ReactNode | undefined;
};
export const BreadcrumbLink: React.FC<Props> = (props) => {
export function BreadcrumbLink(props: Props) {
const { href, label, icon } = props;
return (
<Tooltip tooltipContent={label} position="bottom">
@@ -35,4 +35,4 @@ export const BreadcrumbLink: React.FC<Props> = (props) => {
</li>
</Tooltip>
);
};
}

View File

@@ -6,16 +6,18 @@ type TProps = {
darkerShade?: boolean;
};
export const CodeBlock = ({ children, className, darkerShade }: TProps) => (
<span
className={cn(
"px-0.5 text-xs text-custom-text-300 bg-custom-background-90 font-semibold rounded-md border border-custom-border-100",
{
"text-custom-text-200 bg-custom-background-80 border-custom-border-200": darkerShade,
},
className
)}
>
{children}
</span>
);
export function CodeBlock({ children, className, darkerShade }: TProps) {
return (
<span
className={cn(
"px-0.5 text-xs text-custom-text-300 bg-custom-background-90 font-semibold rounded-md border border-custom-border-100",
{
"text-custom-text-200 bg-custom-background-80 border-custom-border-200": darkerShade,
},
className
)}
>
{children}
</span>
);
}

View File

@@ -13,7 +13,7 @@ type Props = {
onDiscardHref: string;
};
export const ConfirmDiscardModal: React.FC<Props> = (props) => {
export function ConfirmDiscardModal(props: Props) {
const { isOpen, handleClose, onDiscardHref } = props;
return (
@@ -71,4 +71,4 @@ export const ConfirmDiscardModal: React.FC<Props> = (props) => {
</Dialog>
</Transition.Root>
);
};
}

View File

@@ -30,7 +30,7 @@ export type TControllerInputFormField = {
required: boolean;
};
export const ControllerInput: React.FC<Props> = (props) => {
export function ControllerInput(props: Props) {
const { name, control, type, label, description, placeholder, error, required } = props;
// states
const [showPassword, setShowPassword] = useState(false);
@@ -81,4 +81,4 @@ export const ControllerInput: React.FC<Props> = (props) => {
{description && <p className="pt-0.5 text-xs text-custom-text-300">{description}</p>}
</div>
);
};
}

View File

@@ -19,7 +19,7 @@ export type TCopyField = {
description: string | React.ReactNode;
};
export const CopyField: React.FC<Props> = (props) => {
export function CopyField(props: Props) {
const { label, url, description } = props;
return (
@@ -43,4 +43,4 @@ export const CopyField: React.FC<Props> = (props) => {
<div className="text-xs text-custom-text-300">{description}</div>
</div>
);
};
}

View File

@@ -16,32 +16,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">
{image && <img src={image} className="w-52 sm:w-60" 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">
{image && <img src={image} className="w-52 sm:w-60" 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

@@ -2,7 +2,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" ? LogoSpinnerLight : LogoSpinnerDark;
@@ -12,4 +12,4 @@ export const LogoSpinner = () => {
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
</div>
);
};
}

View File

@@ -5,7 +5,7 @@ type TPageHeader = {
description?: string;
};
export const PageHeader: React.FC<TPageHeader> = (props) => {
export function PageHeader(props: TPageHeader) {
const { title = "God Mode - Plane", description = "Plane god mode" } = props;
return (
@@ -14,4 +14,4 @@ export const PageHeader: React.FC<TPageHeader> = (props) => {
<meta name="description" content={description} />
</>
);
};
}

View File

@@ -7,7 +7,7 @@ import { AuthHeader } from "@/app/(all)/(home)/auth-header";
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url";
export const InstanceFailureView: React.FC = observer(() => {
export const InstanceFailureView = observer(function InstanceFailureView() {
const { resolvedTheme } = useTheme();
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;

View File

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

View File

@@ -5,24 +5,26 @@ import { Button } from "@plane/propel/button";
// assets
import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url";
export const InstanceNotReady: React.FC = () => (
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
<div className="w-auto max-w-2xl relative space-y-8 py-10">
<div className="relative flex flex-col justify-center items-center space-y-4">
<h1 className="text-3xl font-bold pb-3">Welcome aboard Plane!</h1>
<img src={PlaneTakeOffImage} alt="Plane Logo" />
<p className="font-medium text-base text-custom-text-400">
Get started by setting up your instance and workspace
</p>
</div>
export function InstanceNotReady() {
return (
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
<div className="w-auto max-w-2xl relative space-y-8 py-10">
<div className="relative flex flex-col justify-center items-center space-y-4">
<h1 className="text-3xl font-bold pb-3">Welcome aboard Plane!</h1>
<img src={PlaneTakeOffImage} alt="Plane Logo" />
<p className="font-medium text-base text-custom-text-400">
Get started by setting up your instance and workspace
</p>
</div>
<div>
<Link href={"/setup/?auth_enabled=0"}>
<Button size="lg" className="w-full">
Get started
</Button>
</Link>
<div>
<Link href={"/setup/?auth_enabled=0"}>
<Button size="lg" className="w-full">
Get started
</Button>
</Link>
</div>
</div>
</div>
</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 InstanceLoading = () => {
export function InstanceLoading() {
const { resolvedTheme } = useTheme();
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerLight : LogoSpinnerDark;
@@ -13,4 +13,4 @@ export const InstanceLoading = () => {
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
</div>
);
};
}

View File

@@ -53,7 +53,7 @@ const defaultFromData: TFormData = {
is_telemetry_enabled: true,
};
export const InstanceSetupForm: React.FC = (props) => {
export function InstanceSetupForm(props) {
const {} = props;
// search params
const searchParams = useSearchParams();
@@ -351,4 +351,4 @@ export const InstanceSetupForm: React.FC = (props) => {
</div>
</>
);
};
}

View File

@@ -12,7 +12,7 @@ import TakeoffIconLight from "@/app/assets/logos/takeoff-icon-light.svg?url";
import { useTheme } from "@/hooks/store";
// icons
export const NewUserPopup = observer(() => {
export const NewUserPopup = observer(function NewUserPopup() {
// hooks
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
// theme

View File

@@ -11,7 +11,7 @@ type TWorkspaceListItemProps = {
workspaceId: string;
};
export const WorkspaceListItem = observer(({ workspaceId }: TWorkspaceListItemProps) => {
export const WorkspaceListItem = observer(function WorkspaceListItem({ workspaceId }: TWorkspaceListItemProps) {
// store hooks
const { getWorkspaceById } = useWorkspace();
// derived values

View File

@@ -1,5 +1,4 @@
"use client";
import React from "react";
// Minimal shim so code using next/image compiles under React Router + Vite
@@ -9,6 +8,8 @@ type NextImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
src: string;
};
const Image: React.FC<NextImageProps> = ({ src, alt = "", ...rest }) => <img src={src} alt={alt} {...rest} />;
function Image({ src, alt = "", ...rest }: NextImageProps) {
return <img src={src} alt={alt} {...rest} />;
}
export default Image;

View File

@@ -1,5 +1,4 @@
"use client";
import React from "react";
import { Link as RRLink } from "react-router";
import { ensureTrailingSlash } from "./helper";
@@ -12,13 +11,8 @@ type NextLinkProps = React.ComponentProps<"a"> & {
shallow?: boolean; // next.js prop, ignored
};
const Link: React.FC<NextLinkProps> = ({
href,
replace,
prefetch: _prefetch,
scroll: _scroll,
shallow: _shallow,
...rest
}) => <RRLink to={ensureTrailingSlash(href)} replace={replace} {...rest} />;
function Link({ href, replace, prefetch: _prefetch, scroll: _scroll, shallow: _shallow, ...rest }: NextLinkProps) {
return <RRLink to={ensureTrailingSlash(href)} replace={replace} {...rest} />;
}
export default Link;

View File

@@ -1,9 +1,8 @@
"use client";
// ui
import { Button } from "@plane/propel/button";
const ErrorPage = () => {
function ErrorPage() {
const handleRetry = () => {
window.location.reload();
};
@@ -42,6 +41,6 @@ const ErrorPage = () => {
</div>
</div>
);
};
}
export default ErrorPage;

View File

@@ -10,7 +10,7 @@ import { usePublish } from "@/hooks/store/publish";
import { useLabel } from "@/hooks/store/use-label";
import { useStates } from "@/hooks/store/use-state";
const IssuesPage = observer(() => {
const IssuesPage = observer(function IssuesPage() {
// params
const params = useParams<{ anchor: string }>();
const { anchor } = params;

View File

@@ -1,22 +1,23 @@
"use client";
// assets
import SomethingWentWrongImage from "@/app/assets/something-went-wrong.svg?url";
const NotFound = () => (
<div className="h-screen w-screen grid place-items-center">
<div className="text-center">
<div className="mx-auto size-32 md:size-52 grid place-items-center rounded-full bg-custom-background-80">
<div className="size-16 md:size-32 grid place-items-center">
<img src={SomethingWentWrongImage} alt="Something went wrong" width={128} height={128} />
function NotFound() {
return (
<div className="h-screen w-screen grid place-items-center">
<div className="text-center">
<div className="mx-auto size-32 md:size-52 grid place-items-center rounded-full bg-custom-background-80">
<div className="size-16 md:size-32 grid place-items-center">
<img src={SomethingWentWrongImage} alt="Something went wrong" width={128} height={128} />
</div>
</div>
<h1 className="mt-8 md:mt-12 text-xl md:text-3xl font-semibold">That didn{"'"}t work</h1>
<p className="mt-2 md:mt-4 text-sm md:text-base">
Check the URL you are entering in the browser{"'"}s address bar and try again.
</p>
</div>
<h1 className="mt-8 md:mt-12 text-xl md:text-3xl font-semibold">That didn{"'"}t work</h1>
<p className="mt-2 md:mt-4 text-sm md:text-base">
Check the URL you are entering in the browser{"'"}s address bar and try again.
</p>
</div>
</div>
);
);
}
export default NotFound;

View File

@@ -11,7 +11,7 @@ import { AuthView } from "@/components/views";
// hooks
import { useUser } from "@/hooks/store/use-user";
const HomePage = observer(() => {
const HomePage = observer(function HomePage() {
const { data: currentUser, isAuthenticated, isInitializing } = useUser();
const searchParams = useSearchParams();
const router = useRouter();

View File

@@ -1,4 +1,6 @@
// plane editor
import type { TCallbackMentionComponentProps } from "@plane/editor";
export const EditorAdditionalMentionsRoot: React.FC<TCallbackMentionComponentProps> = () => null;
export function EditorAdditionalMentionsRoot(_props: TCallbackMentionComponentProps) {
return null;
}

View File

@@ -6,4 +6,6 @@ type Props = {
publishSettings: PublishStore;
};
export const ViewLayoutsRoot = (_props: Props) => <PageNotFound />;
export function ViewLayoutsRoot(_props: Props) {
return <PageNotFound />;
}

View File

@@ -5,4 +5,6 @@ type Props = {
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const ViewNavbarRoot = (props: Props) => <></>;
export function ViewNavbarRoot(props: Props) {
return <></>;
}

View File

@@ -10,7 +10,7 @@ type TAuthBanner = {
handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
};
export const AuthBanner: React.FC<TAuthBanner> = (props) => {
export function AuthBanner(props: TAuthBanner) {
const { bannerData, handleBannerData } = props;
if (!bannerData) return <></>;
@@ -28,4 +28,4 @@ export const AuthBanner: React.FC<TAuthBanner> = (props) => {
</div>
</div>
);
};
}

View File

@@ -27,7 +27,7 @@ const Titles: TAuthHeaderDetails = {
},
};
export const AuthHeader: React.FC<TAuthHeader> = (props) => {
export function AuthHeader(props: TAuthHeader) {
const { authMode } = props;
const getHeaderSubHeader = (mode: EAuthModes | null): TAuthHeaderContent => {
@@ -51,4 +51,4 @@ export const AuthHeader: React.FC<TAuthHeader> = (props) => {
</div>
</>
);
};
}

View File

@@ -32,7 +32,7 @@ import { AuthUniqueCodeForm } from "./unique-code";
const authService = new SitesAuthService();
export const AuthRoot: React.FC = observer(() => {
export const AuthRoot = observer(function AuthRoot() {
// router params
const searchParams = useSearchParams();
const emailParam = searchParams.get("email") || undefined;

View File

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

View File

@@ -35,7 +35,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, nextPath, isSMTPConfigured, handleAuthStep, handleEmailClear, mode } = props;
// ref
const formRef = useRef<HTMLFormElement>(null);

View File

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

View File

@@ -6,7 +6,7 @@ type Props = {
isSignUp?: boolean;
};
export const TermsAndConditions: React.FC<Props> = (props) => {
export function TermsAndConditions(props: Props) {
const { isSignUp = false } = props;
return (
<span className="flex items-center justify-center py-6">
@@ -23,4 +23,4 @@ export const TermsAndConditions: React.FC<Props> = (props) => {
</p>
</span>
);
};
}

View File

@@ -10,7 +10,7 @@ import { UserAvatar } from "@/components/issues/navbar/user-avatar";
// hooks
import { useUser } from "@/hooks/store/use-user";
export const UserLoggedIn = observer(() => {
export const UserLoggedIn = observer(function UserLoggedIn() {
// store hooks
const { data: user } = useUser();

View File

@@ -5,7 +5,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" ? LogoSpinnerLight : LogoSpinnerDark;
@@ -15,4 +15,4 @@ export const LogoSpinner = () => {
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
</div>
);
};
}

View File

@@ -8,7 +8,7 @@ type TPoweredBy = {
disabled?: boolean;
};
export const PoweredBy: React.FC<TPoweredBy> = (props) => {
export function PoweredBy(props: TPoweredBy) {
// props
const { disabled = false } = props;
@@ -27,4 +27,4 @@ export const PoweredBy: React.FC<TPoweredBy> = (props) => {
</div>
</a>
);
};
}

View File

@@ -8,7 +8,7 @@ type Props = {
logo: TLogoProps;
};
export const ProjectLogo: React.FC<Props> = (props) => {
export function ProjectLogo(props: Props) {
const { className, logo } = props;
if (logo.in_use === "icon" && logo.icon)
@@ -31,4 +31,4 @@ export const ProjectLogo: React.FC<Props> = (props) => {
);
return <span />;
};
}

View File

@@ -5,7 +5,7 @@ import { EditorAdditionalMentionsRoot } from "@/plane-web/components/editor";
// local components
import { EditorUserMention } from "./user";
export const EditorMentionsRoot: React.FC<TCallbackMentionComponentProps> = (props) => {
export function EditorMentionsRoot(props: TCallbackMentionComponentProps) {
const { entity_identifier, entity_name } = props;
switch (entity_name) {
@@ -14,4 +14,4 @@ export const EditorMentionsRoot: React.FC<TCallbackMentionComponentProps> = (pro
default:
return <EditorAdditionalMentionsRoot {...props} />;
}
};
}

View File

@@ -9,7 +9,7 @@ type Props = {
id: string;
};
export const EditorUserMention: React.FC<Props> = observer((props) => {
export const EditorUserMention = observer(function EditorUserMention(props: Props) {
const { id } = props;
// store hooks
const { data: currentUser } = useUser();

View File

@@ -32,7 +32,10 @@ type LiteTextEditorWrapperProps = MakeOptional<
}
);
export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapperProps>((props, ref) => {
export const LiteTextEditor = React.forwardRef(function LiteTextEditor(
props: LiteTextEditorWrapperProps,
ref: React.ForwardedRef<EditorRefApi>
) {
const {
anchor,
containerClassName,

View File

@@ -29,7 +29,10 @@ type RichTextEditorWrapperProps = MakeOptional<
}
);
export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProps>((props, ref) => {
export const RichTextEditor = forwardRef(function RichTextEditor(
props: RichTextEditorWrapperProps,
ref: React.ForwardedRef<EditorRefApi>
) {
const {
anchor,
containerClassName,

View File

@@ -19,7 +19,7 @@ type Props = {
const toolbarItems = TOOLBAR_ITEMS.lite;
export const IssueCommentToolbar: React.FC<Props> = (props) => {
export function IssueCommentToolbar(props: Props) {
const { executeCommand, handleSubmit, isCommentEmpty, editorRef, isSubmitting, showSubmitButton } = props;
// states
const [activeStates, setActiveStates] = useState<Record<string, boolean>>({});
@@ -113,4 +113,4 @@ export const IssueCommentToolbar: React.FC<Props> = (props) => {
</div>
</div>
);
};
}

View File

@@ -6,7 +6,7 @@ import { Button } from "@plane/propel/button";
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url";
export const InstanceFailureView: React.FC = () => {
export function InstanceFailureView() {
const { resolvedTheme } = useTheme();
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
@@ -34,4 +34,4 @@ export const InstanceFailureView: React.FC = () => {
</div>
</div>
);
};
}

View File

@@ -17,7 +17,7 @@ type Props = {
export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
export const AppliedFiltersList = observer(function AppliedFiltersList(props: Props) {
const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter } = props;
const { t } = useTranslation();

View File

@@ -10,7 +10,7 @@ type Props = {
values: string[];
};
export const AppliedLabelsFilters: React.FC<Props> = (props) => {
export function AppliedLabelsFilters(props: Props) {
const { handleRemove, labels, values } = props;
return (
@@ -41,4 +41,4 @@ export const AppliedLabelsFilters: React.FC<Props> = (props) => {
})}
</>
);
};
}

View File

@@ -8,7 +8,7 @@ type Props = {
values: TIssuePriorities[];
};
export const AppliedPriorityFilters: React.FC<Props> = (props) => {
export function AppliedPriorityFilters(props: Props) {
const { handleRemove, values } = props;
return (
@@ -30,4 +30,4 @@ export const AppliedPriorityFilters: React.FC<Props> = (props) => {
))}
</>
);
};
}

View File

@@ -15,7 +15,7 @@ type TIssueAppliedFilters = {
anchor: string;
};
export const IssueAppliedFilters: React.FC<TIssueAppliedFilters> = observer((props) => {
export const IssueAppliedFilters = observer(function IssueAppliedFilters(props: TIssueAppliedFilters) {
const { anchor } = props;
// router
const router = useRouter();

View File

@@ -12,7 +12,7 @@ type Props = {
values: string[];
};
export const AppliedStateFilters: React.FC<Props> = observer((props) => {
export const AppliedStateFilters = observer(function AppliedStateFilters(props: Props) {
const { handleRemove, values } = props;
const { sortedStates: states } = useStates();

View File

@@ -13,7 +13,7 @@ type Props = {
placement?: Placement;
};
export const FiltersDropdown: React.FC<Props> = (props) => {
export function FiltersDropdown(props: Props) {
const { children, title = "Dropdown", placement } = props;
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
@@ -62,4 +62,4 @@ export const FiltersDropdown: React.FC<Props> = (props) => {
}}
</Popover>
);
};
}

View File

@@ -9,15 +9,17 @@ interface IFilterHeader {
handleIsPreviewEnabled: () => void;
}
export const FilterHeader = ({ title, isPreviewEnabled, handleIsPreviewEnabled }: IFilterHeader) => (
<div className="sticky top-0 flex items-center justify-between gap-2 bg-custom-background-100">
<div className="flex-grow truncate text-xs font-medium text-custom-text-300">{title}</div>
<button
type="button"
className="grid h-5 w-5 flex-shrink-0 place-items-center rounded hover:bg-custom-background-80"
onClick={handleIsPreviewEnabled}
>
{isPreviewEnabled ? <ChevronUpIcon height={14} width={14} /> : <ChevronDownIcon height={14} width={14} />}
</button>
</div>
);
export function FilterHeader({ title, isPreviewEnabled, handleIsPreviewEnabled }: IFilterHeader) {
return (
<div className="sticky top-0 flex items-center justify-between gap-2 bg-custom-background-100">
<div className="flex-grow truncate text-xs font-medium text-custom-text-300">{title}</div>
<button
type="button"
className="grid h-5 w-5 flex-shrink-0 place-items-center rounded hover:bg-custom-background-80"
onClick={handleIsPreviewEnabled}
>
{isPreviewEnabled ? <ChevronUpIcon height={14} width={14} /> : <ChevronDownIcon height={14} width={14} />}
</button>
</div>
);
}

View File

@@ -12,7 +12,7 @@ type Props = {
multiple?: boolean;
};
export const FilterOption: React.FC<Props> = (props) => {
export function FilterOption(props: Props) {
const { icon, isChecked, multiple = true, onClick, title } = props;
return (
@@ -34,4 +34,4 @@ export const FilterOption: React.FC<Props> = (props) => {
</div>
</button>
);
};
}

View File

@@ -1,5 +1,4 @@
"use client";
import React, { useState } from "react";
// plane imports
import { Loader } from "@plane/ui";
@@ -9,9 +8,9 @@ import type { IIssueLabel } from "@/types/issue";
import { FilterHeader } from "./helpers/filter-header";
import { FilterOption } from "./helpers/filter-option";
const LabelIcons = ({ color }: { color: string }) => (
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
);
function LabelIcons({ color }: { color: string }) {
return <span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />;
}
type Props = {
appliedFilters: string[] | null;
@@ -20,7 +19,7 @@ type Props = {
searchQuery: string;
};
export const FilterLabels: React.FC<Props> = (props) => {
export function FilterLabels(props: Props) {
const { appliedFilters, handleUpdate, labels, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
@@ -82,4 +81,4 @@ export const FilterLabels: React.FC<Props> = (props) => {
)}
</>
);
};
}

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