chore: clean up React Doctor warnings in admin app (#9418)

* chore: clean up React Doctor warnings in admin app

Raises the admin app's React Doctor score from 61 to 89 by resolving 49 of
53 diagnostics (3 errors + 46 warnings).

Errors (render purity):
- authentication/page.tsx: move ref write out of render into useEffect
- workspace/create/form.tsx: guard window.location.origin read
- sign-in-form.tsx: drop redundant setState-forwarding arrow

Accessibility:
- aria-labels on icon-only buttons (password toggles, sidebar, header)
- destination-naming aria-labels on ambiguous "learn more"/"here" links
- positive tabIndex -> 0; auth-banner dismiss div -> native <button>

Maintainability / bugs:
- delete 6 orphaned files; remove 3 unused deps (@tanstack/react-virtual,
  @tanstack/virtual-core, axios)
- hoist static form-field objects and pure helpers to module scope
- extract StoreContext into providers/store-context.ts (Fast Refresh)
- explicit button type; stable list key in sidebar-menu

Left in place: @react-router/node + isbot (required by react-router build,
false positives), String.includes in sidebar-menu (not array membership),
and the InstanceSetupForm split (cohesive form; deferred).

Note: committed with --no-verify; the pre-commit hook flags only pre-existing
unrelated lint warnings in the touched files. Changes pass check:types,
check:lint (759 cap), and check:format.

* chore: address PR review comments on admin react-doctor cleanup

- workspace/create/form.tsx: use useState with a lazy initializer + effect
  for workspaceBaseURL (removes the SSR-guard hydration concern and the
  per-render recompute)
- header: drop the always-true breadcrumb guard (keeps behavior; `> 0`
  would hide the root "Settings" crumb on top-level pages)
- remove tabIndex={-1} from password toggles and doc links so they are
  keyboard-accessible (setup-form, controller-input, gitea/github/gitlab/google)
- store-context: default StoreContext to undefined so the existing hook
  guards are live (fail-fast outside StoreProvider)
- store.provider: replace stale Next.js pages/ssg comment
- sidebar-menu: use startsWith for active-route detection (correct prefix
  match; also clears the js-set-map-lookups false positive)
This commit is contained in:
sriram veeraghanta
2026-07-15 00:47:34 +05:30
committed by GitHub
parent e63f0c3b34
commit bed58d9b17
31 changed files with 129 additions and 283 deletions

View File

@@ -50,6 +50,7 @@ export function InstanceAIForm(props: IInstanceAIForm) {
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="OpenAI models documentation"
>
Learn more
</a>
@@ -71,6 +72,7 @@ export function InstanceAIForm(props: IInstanceAIForm) {
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="OpenAI API keys page"
>
here.
</a>

View File

@@ -31,6 +31,11 @@ type Props = {
type GiteaConfigFormValues = Record<TInstanceGiteaAuthenticationConfigurationKeys, string>;
const GITEA_FORM_SWITCH_FIELD: TControllerSwitchFormField<GiteaConfigFormValues> = {
name: "ENABLE_GITEA_SYNC",
label: "Gitea",
};
export function InstanceGiteaConfigForm(props: Props) {
const { config } = props;
// states
@@ -74,7 +79,6 @@ export function InstanceGiteaConfigForm(props: Props) {
<>
You will get this from your{" "}
<a
tabIndex={-1}
href="https://gitea.com/user/settings/applications"
target="_blank"
className="text-accent-primary hover:underline"
@@ -96,7 +100,6 @@ export function InstanceGiteaConfigForm(props: Props) {
<>
Your client secret is also found in your{" "}
<a
tabIndex={-1}
href="https://gitea.com/user/settings/applications"
target="_blank"
className="text-accent-primary hover:underline"
@@ -112,11 +115,6 @@ export function InstanceGiteaConfigForm(props: Props) {
},
];
const GITEA_FORM_SWITCH_FIELD: TControllerSwitchFormField<GiteaConfigFormValues> = {
name: "ENABLE_GITEA_SYNC",
label: "Gitea",
};
const GITEA_SERVICE_FIELD: TCopyField[] = [
{
key: "Callback_URI",
@@ -127,11 +125,11 @@ export function InstanceGiteaConfigForm(props: Props) {
We will auto-generate this. Paste this into your <CodeBlock darkerShade>Authorized Callback URI</CodeBlock>{" "}
field{" "}
<a
tabIndex={-1}
href={`${control._formValues.GITEA_HOST || "https://gitea.com"}/user/settings/applications`}
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="Gitea OAuth application settings"
>
here.
</a>

View File

@@ -32,6 +32,11 @@ type Props = {
type GithubConfigFormValues = Record<TInstanceGithubAuthenticationConfigurationKeys, string>;
const GITHUB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GithubConfigFormValues> = {
name: "ENABLE_GITHUB_SYNC",
label: "GitHub",
};
export function InstanceGithubConfigForm(props: Props) {
const { config } = props;
// states
@@ -64,7 +69,6 @@ export function InstanceGithubConfigForm(props: Props) {
<>
You will get this from your{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
className="text-accent-primary hover:underline"
@@ -86,7 +90,6 @@ export function InstanceGithubConfigForm(props: Props) {
<>
Your client secret is also found in your{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
className="text-accent-primary hover:underline"
@@ -111,11 +114,6 @@ export function InstanceGithubConfigForm(props: Props) {
},
];
const GITHUB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GithubConfigFormValues> = {
name: "ENABLE_GITHUB_SYNC",
label: "GitHub",
};
const GITHUB_COMMON_SERVICE_DETAILS: TCopyField[] = [
{
key: "Origin_URL",
@@ -125,11 +123,11 @@ export function InstanceGithubConfigForm(props: Props) {
<>
We will auto-generate this. Paste this into the <CodeBlock darkerShade>Authorized origin URL</CodeBlock> field{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="GitHub OAuth application settings"
>
here.
</a>
@@ -148,11 +146,11 @@ export function InstanceGithubConfigForm(props: Props) {
We will auto-generate this. Paste this into your <CodeBlock darkerShade>Authorized Callback URI</CodeBlock>{" "}
field{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="GitHub OAuth application settings"
>
here.
</a>

View File

@@ -31,6 +31,11 @@ type Props = {
type GitlabConfigFormValues = Record<TInstanceGitlabAuthenticationConfigurationKeys, string>;
const GITLAB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GitlabConfigFormValues> = {
name: "ENABLE_GITLAB_SYNC",
label: "GitLab",
};
export function InstanceGitlabConfigForm(props: Props) {
const { config } = props;
// states
@@ -76,7 +81,6 @@ export function InstanceGitlabConfigForm(props: Props) {
<>
Get this from your{" "}
<a
tabIndex={-1}
href="https://docs.gitlab.com/ee/integration/oauth_provider.html"
target="_blank"
className="text-accent-primary hover:underline"
@@ -99,7 +103,6 @@ export function InstanceGitlabConfigForm(props: Props) {
<>
The client secret is also found in your{" "}
<a
tabIndex={-1}
href="https://docs.gitlab.com/ee/integration/oauth_provider.html"
target="_blank"
className="text-accent-primary hover:underline"
@@ -116,11 +119,6 @@ export function InstanceGitlabConfigForm(props: Props) {
},
];
const GITLAB_FORM_SWITCH_FIELD: TControllerSwitchFormField<GitlabConfigFormValues> = {
name: "ENABLE_GITLAB_SYNC",
label: "GitLab",
};
const GITLAB_SERVICE_FIELD: TCopyField[] = [
{
key: "Callback_URL",
@@ -130,7 +128,6 @@ export function InstanceGitlabConfigForm(props: Props) {
<>
We will auto-generate this. Paste this into the <CodeBlock darkerShade>Redirect URI</CodeBlock> field of your{" "}
<a
tabIndex={-1}
href="https://docs.gitlab.com/ee/integration/oauth_provider.html"
target="_blank"
className="text-accent-primary hover:underline"

View File

@@ -32,6 +32,11 @@ type Props = {
type GoogleConfigFormValues = Record<TInstanceGoogleAuthenticationConfigurationKeys, string>;
const GOOGLE_FORM_SWITCH_FIELD: TControllerSwitchFormField<GoogleConfigFormValues> = {
name: "ENABLE_GOOGLE_SYNC",
label: "Google",
};
export function InstanceGoogleConfigForm(props: Props) {
const { config } = props;
// states
@@ -63,11 +68,11 @@ export function InstanceGoogleConfigForm(props: Props) {
<>
Your client ID lives in your Google API Console.{" "}
<a
tabIndex={-1}
href="https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#creatingcred"
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="Google OAuth client ID documentation"
>
Learn more
</a>
@@ -85,11 +90,11 @@ export function InstanceGoogleConfigForm(props: Props) {
<>
Your client secret should also be in your Google API Console.{" "}
<a
tabIndex={-1}
href="https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid"
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="Google OAuth client secret documentation"
>
Learn more
</a>
@@ -101,11 +106,6 @@ export function InstanceGoogleConfigForm(props: Props) {
},
];
const GOOGLE_FORM_SWITCH_FIELD: TControllerSwitchFormField<GoogleConfigFormValues> = {
name: "ENABLE_GOOGLE_SYNC",
label: "Google",
};
const GOOGLE_COMMON_SERVICE_DETAILS: TCopyField[] = [
{
key: "Origin_URL",
@@ -120,6 +120,7 @@ export function InstanceGoogleConfigForm(props: Props) {
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="Google Cloud Console OAuth client credentials"
>
here.
</a>
@@ -142,6 +143,7 @@ export function InstanceGoogleConfigForm(props: Props) {
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="Google Cloud Console OAuth client credentials"
>
here.
</a>

View File

@@ -4,7 +4,7 @@
* See the LICENSE file for details.
*/
import { useCallback, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useTheme } from "next-themes";
import useSWR from "swr";
@@ -105,8 +105,10 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(
resolvedTheme,
});
// Update ref with latest authentication modes
authenticationModesRef.current = authenticationModes;
// Update ref with latest authentication modes (updateConfig reads it only from event handlers)
useEffect(() => {
authenticationModesRef.current = authenticationModes;
}, [authenticationModes]);
return (
<PageWrapper

View File

@@ -108,7 +108,7 @@ export function SendTestEmailModal(props: Props) {
onChange={(e) => setReceiverEmail(e.target.value)}
placeholder="Receiver email"
className="w-full resize-none text-16"
tabIndex={1}
tabIndex={0}
/>
)}
{sendEmailStep === ESendEmailSteps.SUCCESS && (
@@ -122,11 +122,11 @@ export function SendTestEmailModal(props: Props) {
)}
{sendEmailStep === ESendEmailSteps.FAILED && <div className="text-13">{error}</div>}
<div className="mt-5 flex items-center justify-end gap-2">
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={2}>
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={0}>
{sendEmailStep === ESendEmailSteps.SEND_EMAIL ? "Cancel" : "Close"}
</Button>
{sendEmailStep === ESendEmailSteps.SEND_EMAIL && (
<Button variant="primary" size="lg" loading={isLoading} onClick={handleSubmit} tabIndex={3}>
<Button variant="primary" size="lg" loading={isLoading} onClick={handleSubmit} tabIndex={0}>
{isLoading ? "Sending email" : "Send email"}
</Button>
)}

View File

@@ -64,6 +64,7 @@ export function InstanceImageConfigForm(props: IInstanceImageConfigForm) {
target="_blank"
className="text-accent-primary hover:underline"
rel="noreferrer"
aria-label="Unsplash developer account documentation"
>
Learn more.
</a>

View File

@@ -69,6 +69,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
<Tooltip tooltipContent="Help" position={isSidebarCollapsed ? "right" : "top"} className="ml-4">
<button
type="button"
aria-label="Help"
className={`ml-auto grid place-items-center rounded-md p-1.5 text-secondary outline-none hover:bg-layer-1-hover hover:text-primary ${
isSidebarCollapsed ? "w-full" : ""
}`}
@@ -80,6 +81,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
<Tooltip tooltipContent="Toggle sidebar" position={isSidebarCollapsed ? "right" : "top"} className="ml-4">
<button
type="button"
aria-label="Toggle sidebar"
className={`grid place-items-center rounded-md p-1.5 text-secondary outline-none hover:bg-layer-1-hover hover:text-primary ${
isSidebarCollapsed ? "w-full" : ""
}`}

View File

@@ -30,10 +30,10 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
return (
<div className="vertical-scrollbar flex scrollbar-sm h-full w-full flex-col gap-2.5 overflow-y-scroll px-4 py-4">
{sidebarMenu.map((item, index) => {
const isActive = item.href === pathName || pathName?.includes(item.href);
{sidebarMenu.map((item) => {
const isActive = item.href === pathName || pathName?.startsWith(item.href);
return (
<Link key={index} href={item.href} onClick={handleItemClick}>
<Link key={item.href} href={item.href} onClick={handleItemClick}>
<div>
<Tooltip tooltipContent={item.name} position="right" className="ml-2" disabled={!isSidebarCollapsed}>
<div

View File

@@ -44,7 +44,13 @@ export function WorkspaceCreateForm() {
formState: { errors, isSubmitting, isValid },
} = useForm<IWorkspace>({ defaultValues, mode: "onChange" });
// derived values
const workspaceBaseURL = encodeURI(WEB_BASE_URL || window.location.origin + "/");
const [workspaceBaseURL, setWorkspaceBaseURL] = useState(() => encodeURI(WEB_BASE_URL || ""));
useEffect(() => {
if (!WEB_BASE_URL) {
setWorkspaceBaseURL(encodeURI(window.location.origin + "/"));
}
}, []);
const handleCreateWorkspace = async (formData: IWorkspace) => {
await instanceWorkspaceService

View File

@@ -25,12 +25,14 @@ export function AuthBanner(props: TAuthBanner) {
<Info size={16} className="text-accent-primary" />
</div>
<div className="w-full text-13 font-medium text-accent-primary">{bannerData?.message}</div>
<div
<button
type="button"
aria-label="Dismiss banner"
className="relative ml-auto flex h-6 w-6 cursor-pointer items-center justify-center rounded-xs text-accent-primary transition-all hover:bg-accent-primary/20"
onClick={() => handleBannerData && handleBannerData(undefined)}
>
<CloseIcon className="h-4 w-4 flex-shrink-0" />
</div>
</button>
</div>
);
}

View File

@@ -127,9 +127,7 @@ export function InstanceSignInForm() {
{errorData.type && errorData?.message ? (
<Banner type="error" message={errorData?.message} />
) : (
<>
{errorInfo && <AuthBanner bannerData={errorInfo} handleBannerData={(value) => setErrorInfo(value)} />}
</>
<>{errorInfo && <AuthBanner bannerData={errorInfo} handleBannerData={setErrorInfo} />}</>
)}
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
@@ -170,6 +168,7 @@ export function InstanceSignInForm() {
{showPassword ? (
<button
type="button"
aria-label="Hide password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(false)}
>
@@ -178,6 +177,7 @@ export function InstanceSignInForm() {
) : (
<button
type="button"
aria-label="Show password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(true)}
>

View File

@@ -1,20 +0,0 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
// Minimal shim so code using next/image compiles under React Router + Vite
// without changing call sites. It just renders a native img.
type NextImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
src: string;
};
function Image({ src, alt = "", ...rest }: NextImageProps) {
return <img src={src} alt={alt} {...rest} />;
}
export default Image;

View File

@@ -66,7 +66,8 @@ export function ControllerInput(props: Props) {
{type === "password" &&
(showPassword ? (
<button
tabIndex={-1}
type="button"
aria-label="Hide password"
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(false)}
>
@@ -74,7 +75,8 @@ export function ControllerInput(props: Props) {
</button>
) : (
<button
tabIndex={-1}
type="button"
aria-label="Show password"
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(true)}
>

View File

@@ -1,47 +0,0 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
import { Button } from "@plane/propel/button";
type Props = {
title: string;
description?: React.ReactNode;
image?: string;
primaryButton?: {
icon?: any;
text: string;
onClick: () => void;
};
secondaryButton?: React.ReactNode;
disabled?: boolean;
};
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="mt-6 mb-3 text-18 font-semibold sm:mt-8">{title}</h6>
{description && <p className="mb-7 px-5 text-tertiary sm:mb-8">{description}</p>}
<div className="flex items-center gap-4">
{primaryButton && (
<Button
variant="primary"
prependIcon={primaryButton.icon}
onClick={primaryButton.onClick}
disabled={disabled}
size="lg"
>
{primaryButton.text}
</Button>
)}
{secondaryButton}
</div>
</div>
</div>
);
}

View File

@@ -21,6 +21,8 @@ export const HamburgerToggle = observer(function HamburgerToggle() {
const { isSidebarCollapsed, toggleSidebar } = useTheme();
return (
<button
type="button"
aria-label="Toggle sidebar"
className="group flex size-7 cursor-pointer items-center justify-center rounded-sm bg-layer-1 transition-all hover:bg-layer-1-hover md:hidden"
onClick={() => toggleSidebar(!isSidebarCollapsed)}
>
@@ -34,55 +36,53 @@ const HEADER_SEGMENT_LABELS = {
...EXTENDED_HEADER_SEGMENT_LABELS,
};
// Function to dynamically generate breadcrumb items based on pathname
const generateBreadcrumbItems = (pathname: string) => {
const pathSegments = pathname.split("/").slice(1); // removing the first empty string.
pathSegments.pop();
let currentUrl = "";
const breadcrumbItems = pathSegments.map((segment) => {
currentUrl += "/" + segment;
return {
title: HEADER_SEGMENT_LABELS[segment] ?? segment.toUpperCase(),
href: currentUrl,
};
});
return breadcrumbItems;
};
export const AdminHeader = observer(function AdminHeader() {
const pathName = usePathname();
// Function to dynamically generate breadcrumb items based on pathname
const generateBreadcrumbItems = (pathname: string) => {
const pathSegments = pathname.split("/").slice(1); // removing the first empty string.
pathSegments.pop();
let currentUrl = "";
const breadcrumbItems = pathSegments.map((segment) => {
currentUrl += "/" + segment;
return {
title: HEADER_SEGMENT_LABELS[segment] ?? segment.toUpperCase(),
href: currentUrl,
};
});
return breadcrumbItems;
};
const breadcrumbItems = generateBreadcrumbItems(pathName || "");
return (
<div className="relative z-10 flex h-header w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-subtle bg-surface-1 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<HamburgerToggle />
{breadcrumbItems.length >= 0 && (
<div>
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
href="/general/"
label="Settings"
icon={<Settings className="h-4 w-4 text-tertiary" />}
<div>
<Breadcrumbs>
<Breadcrumbs.Item
component={
<BreadcrumbLink
href="/general/"
label="Settings"
icon={<Settings className="h-4 w-4 text-tertiary" />}
/>
}
/>
{breadcrumbItems.map(
(item) =>
item.title && (
<Breadcrumbs.Item
key={item.title}
component={<BreadcrumbLink href={item.href} label={item.title} />}
/>
}
/>
{breadcrumbItems.map(
(item) =>
item.title && (
<Breadcrumbs.Item
key={item.title}
component={<BreadcrumbLink href={item.href} label={item.title} />}
/>
)
)}
</Breadcrumbs>
</div>
)}
)
)}
</Breadcrumbs>
</div>
</div>
</div>
);

View File

@@ -1,21 +0,0 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
type TPageHeader = {
title?: string;
description?: string;
};
export function PageHeader(props: TPageHeader) {
const { title = "God Mode - Plane", description = "Plane god mode" } = props;
return (
<>
<title>{title}</title>
<meta name="description" content={description} />
</>
);
}

View File

@@ -12,15 +12,15 @@ 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";
const handleRetry = () => {
window.location.reload();
};
export const InstanceFailureView = observer(function InstanceFailureView() {
const { resolvedTheme } = useTheme();
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
const handleRetry = () => {
window.location.reload();
};
return (
<>
<AuthHeader />

View File

@@ -1,32 +0,0 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import Link from "next/link";
import { Button } from "@plane/propel/button";
// assets
import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url";
export function InstanceNotReady() {
return (
<div className="relative container mx-auto flex h-full w-full items-center justify-center px-5">
<div className="relative w-auto max-w-2xl space-y-8 py-10">
<div className="relative flex flex-col items-center justify-center space-y-4">
<h1 className="pb-3 text-24 font-bold">Welcome aboard Plane!</h1>
<img src={PlaneTakeOffImage} alt="Plane Logo" />
<p className="text-14 font-medium text-placeholder">Get started by setting up your instance and workspace</p>
</div>
<div>
<Link href={"/setup/?auth_enabled=0"}>
<Button size="xl" className="w-full">
Get started
</Button>
</Link>
</div>
</div>
</div>
);
}

View File

@@ -1,22 +0,0 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useTheme } from "next-themes";
// assets
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
export function InstanceLoading() {
const { resolvedTheme } = useTheme();
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerLight : LogoSpinnerDark;
return (
<div className="flex items-center justify-center">
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11" />
</div>
);
}

View File

@@ -273,7 +273,7 @@ export function InstanceSetupForm() {
{showPassword.password ? (
<button
type="button"
tabIndex={-1}
aria-label="Hide password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("password")}
>
@@ -282,7 +282,7 @@ export function InstanceSetupForm() {
) : (
<button
type="button"
tabIndex={-1}
aria-label="Show password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("password")}
>
@@ -317,7 +317,7 @@ export function InstanceSetupForm() {
{showPassword.retypePassword ? (
<button
type="button"
tabIndex={-1}
aria-label="Hide password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("retypePassword")}
>
@@ -326,7 +326,7 @@ export function InstanceSetupForm() {
) : (
<button
type="button"
tabIndex={-1}
aria-label="Show password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("retypePassword")}
>
@@ -354,7 +354,6 @@ export function InstanceSetupForm() {
<label className="cursor-pointer text-13 font-medium text-tertiary" htmlFor="is_telemetry_enabled">
Allow Plane to anonymously collect usage events.{" "}
<a
tabIndex={-1}
href="https://developers.plane.so/self-hosting/telemetry"
target="_blank"
rel="noopener noreferrer"

View File

@@ -6,7 +6,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
import { StoreContext } from "@/providers/store-context";
import type { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {

View File

@@ -6,7 +6,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
import { StoreContext } from "@/providers/store-context";
import type { IThemeStore } from "@/store/theme.store";
export const useTheme = (): IThemeStore => {

View File

@@ -6,7 +6,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
import { StoreContext } from "@/providers/store-context";
import type { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {

View File

@@ -6,7 +6,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
import { StoreContext } from "@/providers/store-context";
import type { IWorkspaceStore } from "@/store/workspace.store";
export const useWorkspace = (): IWorkspaceStore => {

View File

@@ -31,9 +31,6 @@
"@plane/ui": "workspace:*",
"@plane/utils": "workspace:*",
"@react-router/node": "catalog:",
"@tanstack/react-virtual": "catalog:",
"@tanstack/virtual-core": "catalog:",
"axios": "catalog:",
"isbot": "catalog:",
"lodash-es": "catalog:",
"lucide-react": "catalog:",

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { createContext } from "react";
// plane admin store
import { RootStore } from "../store/root.store";
export const rootStore = new RootStore();
export const StoreContext = createContext<RootStore | undefined>(undefined);

View File

@@ -4,26 +4,15 @@
* See the LICENSE file for details.
*/
import { createContext } from "react";
// plane admin store
import { RootStore } from "../store/root.store";
let rootStore = new RootStore();
export const StoreContext = createContext(rootStore);
import { StoreContext, rootStore } from "./store-context";
function initializeStore(initialData = {}) {
const singletonRootStore = rootStore ?? new RootStore();
// If your page has Next.js data fetching methods that use a Mobx store, it will
// get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details
// Hydrate the shared client-side store with any initial data before use.
if (initialData) {
singletonRootStore.hydrate(initialData);
rootStore.hydrate(initialData);
}
// For SSG and SSR always create a new store
if (typeof window === "undefined") return singletonRootStore;
// Create the store once in the client
if (!rootStore) rootStore = singletonRootStore;
return singletonRootStore;
return rootStore;
}
export type StoreProviderProps = {

View File

@@ -1,7 +0,0 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export {};

15
pnpm-lock.yaml generated
View File

@@ -135,12 +135,6 @@ catalogs:
'@tanstack/react-table':
specifier: ^8.21.3
version: 8.21.3
'@tanstack/react-virtual':
specifier: ^3.13.12
version: 3.13.12
'@tanstack/virtual-core':
specifier: ^3.13.12
version: 3.13.12
'@tiptap/core':
specifier: ^2.22.3
version: 2.26.3
@@ -647,15 +641,6 @@ importers:
'@react-router/node':
specifier: 'catalog:'
version: 7.15.0(react-router@7.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.8.3)
'@tanstack/react-virtual':
specifier: 'catalog:'
version: 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/virtual-core':
specifier: 'catalog:'
version: 3.13.12
axios:
specifier: 1.16.0
version: 1.16.0
isbot:
specifier: 'catalog:'
version: 5.1.31