[WEB-5042] feat: sites vite migration (#7965)

This commit is contained in:
Prateek Shourya
2025-11-06 13:58:24 +05:30
committed by GitHub
parent 315e1d5eb0
commit 118ecc81ba
126 changed files with 1062 additions and 739 deletions

4
.gitignore vendored
View File

@@ -103,6 +103,10 @@ storybook-static
CLAUDE.md
build/
.react-router/
AGENTS.md
build/
.react-router/
AGENTS.md

View File

@@ -1,5 +1,5 @@
"use client";
import type { FC } from "react";
import { useForm } from "react-hook-form";
import { Lightbulb } from "lucide-react";
import { Button } from "@plane/propel/button";
@@ -17,7 +17,7 @@ type IInstanceAIForm = {
type AIFormValues = Record<TInstanceAIConfigurationKeys, string>;
export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
export const InstanceAIForm: React.FC<IInstanceAIForm> = (props) => {
const { config } = props;
// store
const { updateInstanceConfigurations } = useInstance();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useState } from "react";
import { isEmpty } from "lodash-es";
import Link from "next/link";
@@ -29,7 +28,7 @@ type Props = {
type GithubConfigFormValues = Record<TInstanceGithubAuthenticationConfigurationKeys, string>;
export const InstanceGithubConfigForm: FC<Props> = (props) => {
export const InstanceGithubConfigForm: React.FC<Props> = (props) => {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);

View File

@@ -1,4 +1,3 @@
import type { FC } from "react";
import { useState } from "react";
import { isEmpty } from "lodash-es";
import Link from "next/link";
@@ -25,7 +24,7 @@ type Props = {
type GitlabConfigFormValues = Record<TInstanceGitlabAuthenticationConfigurationKeys, string>;
export const InstanceGitlabConfigForm: FC<Props> = (props) => {
export const InstanceGitlabConfigForm: React.FC<Props> = (props) => {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);

View File

@@ -1,5 +1,5 @@
"use client";
import type { FC } from "react";
import { useState } from "react";
import { isEmpty } from "lodash-es";
import Link from "next/link";
@@ -27,7 +27,7 @@ type Props = {
type GoogleConfigFormValues = Record<TInstanceGoogleAuthenticationConfigurationKeys, string>;
export const InstanceGoogleConfigForm: FC<Props> = (props) => {
export const InstanceGoogleConfigForm: React.FC<Props> = (props) => {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);

View File

@@ -1,7 +1,6 @@
"use client";
import type { FC } from "react";
import React, { useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
// types
import { Button } from "@plane/propel/button";
@@ -31,7 +30,7 @@ const EMAIL_SECURITY_OPTIONS: { [key in TEmailSecurityKeys]: string } = {
NONE: "No email security",
};
export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
export const InstanceEmailForm: React.FC<IInstanceEmailForm> = (props) => {
const { config } = props;
// states
const [isSendTestEmailModalOpen, setIsSendTestEmailModalOpen] = useState(false);

View File

@@ -1,5 +1,4 @@
import type { FC } from "react";
import React, { useEffect, useState } from "react";
import { useEffect, useState, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
// plane imports
import { Button } from "@plane/propel/button";
@@ -20,7 +19,7 @@ enum ESendEmailSteps {
const instanceService = new InstanceService();
export const SendTestEmailModal: FC<Props> = (props) => {
export const SendTestEmailModal: React.FC<Props> = (props) => {
const { isOpen, handleClose } = props;
// state
@@ -62,10 +61,10 @@ export const SendTestEmailModal: FC<Props> = (props) => {
};
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
@@ -78,7 +77,7 @@ export const SendTestEmailModal: FC<Props> = (props) => {
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="my-10 flex justify-center p-4 text-center sm:p-0 md:my-20">
<Transition.Child
as={React.Fragment}
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"

View File

@@ -1,5 +1,4 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { Telescope } from "lucide-react";
@@ -20,7 +19,7 @@ export interface IGeneralConfigurationForm {
instanceAdmins: IInstanceAdmin[];
}
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer((props) => {
export const GeneralConfigurationForm: React.FC<IGeneralConfigurationForm> = observer((props) => {
const { instance, instanceAdmins } = props;
// hooks
const { instanceConfigurations, updateInstanceInfo, updateInstanceConfigurations } = useInstance();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
@@ -14,7 +13,7 @@ type TIntercomConfig = {
isTelemetryEnabled: boolean;
};
export const IntercomConfig: FC<TIntercomConfig> = observer((props) => {
export const IntercomConfig: React.FC<TIntercomConfig> = observer((props) => {
const { isTelemetryEnabled } = props;
// hooks
const { instanceConfigurations, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { Menu, Settings } from "lucide-react";
@@ -11,7 +10,7 @@ import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
// hooks
import { useTheme } from "@/hooks/store";
export const HamburgerToggle: FC = observer(() => {
export const HamburgerToggle = observer(() => {
const { isSidebarCollapsed, toggleSidebar } = useTheme();
return (
<div
@@ -23,7 +22,7 @@ export const HamburgerToggle: FC = observer(() => {
);
});
export const AdminHeader: FC = observer(() => {
export const AdminHeader = observer(() => {
const pathName = usePathname();
const getHeaderTitle = (pathName: string) => {

View File

@@ -1,5 +1,4 @@
"use client";
import type { FC } from "react";
import { useForm } from "react-hook-form";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
@@ -15,7 +14,7 @@ type IInstanceImageConfigForm = {
type ImageConfigFormValues = Record<TInstanceImageConfigurationKeys, string>;
export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) => {
export const InstanceImageConfigForm: React.FC<IInstanceImageConfigForm> = (props) => {
const { config } = props;
// store hooks
const { updateInstanceConfigurations } = useInstance();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useState, useRef } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
@@ -35,7 +34,7 @@ const helpOptions = [
},
];
export const AdminSidebarHelpSection: FC = observer(() => {
export const AdminSidebarHelpSection: React.FC = observer(() => {
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// store

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useEffect, useRef } from "react";
import { observer } from "mobx-react";
// plane helpers
@@ -12,7 +11,7 @@ import { AdminSidebarDropdown } from "./sidebar-dropdown";
import { AdminSidebarHelpSection } from "./sidebar-help-section";
import { AdminSidebarMenu } from "./sidebar-menu";
export const AdminSidebar: FC = observer(() => {
export const AdminSidebar = observer(() => {
// store
const { isSidebarCollapsed, toggleSidebar } = useTheme();

View File

@@ -1,4 +1,3 @@
import type { FC } from "react";
import { Info } from "lucide-react";
// plane constants
import type { TAdminAuthErrorInfo } from "@plane/constants";
@@ -10,7 +9,7 @@ type TAuthBanner = {
handleBannerData?: (bannerData: TAdminAuthErrorInfo | undefined) => void;
};
export const AuthBanner: FC<TAuthBanner> = (props) => {
export const AuthBanner: React.FC<TAuthBanner> = (props) => {
const { bannerData, handleBannerData } = props;
if (!bannerData) return <></>;

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
import { Eye, EyeOff } from "lucide-react";
@@ -46,7 +45,7 @@ const defaultFromData: TFormData = {
password: "",
};
export const InstanceSignInForm: FC = () => {
export const InstanceSignInForm: React.FC = () => {
// search params
const searchParams = useSearchParams();
const emailParam = searchParams.get("email") || undefined;

View File

@@ -1,14 +1,9 @@
import type { FC, ReactNode } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
import { useInstance } from "@/hooks/store";
type InstanceProviderProps = {
children: ReactNode;
};
export const InstanceProvider: FC<InstanceProviderProps> = observer((props) => {
export const InstanceProvider = observer<React.FC<React.PropsWithChildren>>((props) => {
const { children } = props;
// store hooks
const { fetchInstanceInfo } = useInstance();

View File

@@ -1,6 +1,5 @@
"use client";
import type { ReactNode } from "react";
import { createContext } from "react";
// plane admin store
import { RootStore } from "@/plane-admin/store/root.store";
@@ -24,7 +23,7 @@ function initializeStore(initialData = {}) {
}
export type StoreProviderProps = {
children: ReactNode;
children: React.ReactNode;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialState?: any;
};

View File

@@ -1,17 +1,12 @@
"use client";
import type { FC, ReactNode } from "react";
import { useEffect } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
import { useInstance, useTheme, useUser } from "@/hooks/store";
interface IUserProvider {
children: ReactNode;
}
export const UserProvider: FC<IUserProvider> = observer(({ children }) => {
export const UserProvider = observer<React.FC<React.PropsWithChildren>>(({ children }) => {
// hooks
const { isSidebarCollapsed, toggleSidebar } = useTheme();
const { currentUser, fetchCurrentUser } = useUser();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
// helpers
import { cn } from "@plane/utils";
@@ -14,7 +13,7 @@ type Props = {
unavailable?: boolean;
};
export const AuthenticationMethodCard: FC<Props> = (props) => {
export const AuthenticationMethodCard: React.FC<Props> = (props) => {
const { name, description, icon, config, disabled = false, withBorder = true, unavailable = false } = props;
return (

View File

@@ -1,6 +1,5 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
// icons

View File

@@ -1,6 +1,5 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
// icons

View File

@@ -7,7 +7,7 @@ import { Button } from "@plane/propel/button";
type Props = {
title: string;
description?: React.ReactNode;
image?: any;
image?: string;
primaryButton?: {
icon?: any;
text: string;

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
// icons
@@ -54,7 +53,7 @@ const defaultFromData: TFormData = {
is_telemetry_enabled: true,
};
export const InstanceSetupForm: FC = (props) => {
export const InstanceSetupForm: React.FC = (props) => {
const {} = props;
// search params
const searchParams = useSearchParams();

View File

@@ -0,0 +1 @@
export {};

View File

@@ -1,9 +1,14 @@
module.exports = {
root: true,
extends: ["@plane/eslint-config/next.js"],
ignorePatterns: [
"build/**",
"dist/**",
".vite/**",
],
rules: {
"no-duplicate-imports": "off",
"import/no-duplicates": ["error", { "prefer-inline": false }],
"import/no-duplicates": ["error", {"prefer-inline": false}],
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/consistent-type-imports": [
@@ -16,3 +21,5 @@ module.exports = {
],
},
};

View File

@@ -12,6 +12,9 @@
/.next/
/out/
# react-router
/.react-router/
# production
/build

View File

@@ -1,103 +1,86 @@
# syntax=docker/dockerfile:1.7
FROM node:22-alpine AS base
# Setup pnpm package manager with corepack and configure global bin directory for caching
WORKDIR /app
ENV TURBO_TELEMETRY_DISABLED=1
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
ENV CI=1
RUN corepack enable pnpm
# =========================================================================== #
# *****************************************************************************
# STAGE 1: Build the project
# *****************************************************************************
FROM base AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app
ARG TURBO_VERSION=2.5.6
RUN corepack enable pnpm && pnpm add -g turbo@${TURBO_VERSION}
RUN pnpm add -g turbo@2.5.8
COPY . .
# Create a pruned workspace for just the space app
RUN turbo prune --scope=space --docker
# *****************************************************************************
# STAGE 2: Install dependencies & build the project
# *****************************************************************************
# =========================================================================== #
FROM base AS installer
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Build in production mode; we still install dev deps explicitly below
ENV NODE_ENV=production
# Public envs required at build time (pick up via process.env)
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_API_BASE_PATH="/api"
ENV NEXT_PUBLIC_API_BASE_PATH=$NEXT_PUBLIC_API_BASE_PATH
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_LIVE_BASE_URL=""
ENV NEXT_PUBLIC_LIVE_BASE_URL=$NEXT_PUBLIC_LIVE_BASE_URL
ARG NEXT_PUBLIC_LIVE_BASE_PATH="/live"
ENV NEXT_PUBLIC_LIVE_BASE_PATH=$NEXT_PUBLIC_LIVE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_PATH=""
ENV NEXT_PUBLIC_WEB_BASE_PATH=$NEXT_PUBLIC_WEB_BASE_PATH
ARG NEXT_PUBLIC_WEBSITE_URL="https://plane.so"
ENV NEXT_PUBLIC_WEBSITE_URL=$NEXT_PUBLIC_WEBSITE_URL
ARG NEXT_PUBLIC_SUPPORT_EMAIL="support@plane.so"
ENV NEXT_PUBLIC_SUPPORT_EMAIL=$NEXT_PUBLIC_SUPPORT_EMAIL
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN corepack enable pnpm
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm fetch --store-dir=/pnpm/store
# Fetch dependencies to cache store, then install offline with dev deps
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm fetch --store-dir=/pnpm/store
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED=1
ENV TURBO_TELEMETRY_DISABLED=1
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store --prod=false
# Build only the space package
RUN pnpm turbo run build --filter=space
# *****************************************************************************
# STAGE 3: Copy the project and start it
# *****************************************************************************
FROM base AS runner
WORKDIR /app
# =========================================================================== #
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
FROM nginx:1.27-alpine AS production
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer /app/apps/space/.next/standalone ./
COPY --from=installer /app/apps/space/.next/static ./apps/space/.next/static
COPY --from=installer /app/apps/space/public ./apps/space/public
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED=1
ENV TURBO_TELEMETRY_DISABLED=1
COPY apps/space/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=installer /app/apps/space/build/client /usr/share/nginx/html/spaces
EXPOSE 3000
CMD ["node", "apps/space/server.js"]
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -fsS http://127.0.0.1:3000/ >/dev/null || exit 1
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,41 +0,0 @@
import { notFound, redirect } from "next/navigation";
// plane imports
import { SitesProjectPublishService } from "@plane/services";
import type { TProjectPublishSettings } from "@plane/types";
const publishService = new SitesProjectPublishService();
type Props = {
params: {
workspaceSlug: string;
projectId: string;
};
searchParams: Record<"board" | "peekId", string | string[] | undefined>;
};
export default async function IssuesPage(props: Props) {
const { params, searchParams } = props;
// query params
const { workspaceSlug, projectId } = params;
const { board, peekId } = searchParams;
let response: TProjectPublishSettings | undefined = undefined;
try {
response = await publishService.retrieveSettingsByProjectId(workspaceSlug, projectId);
} catch (error) {
console.error("Error fetching project publish settings:", error);
notFound();
}
let url = "";
if (response?.entity_name === "project") {
url = `/issues/${response?.anchor}`;
const params = new URLSearchParams();
if (board) params.append("board", String(board));
if (peekId) params.append("peekId", String(peekId));
if (params.toString()) url += `?${params.toString()}`;
redirect(url);
} else {
notFound();
}
}

View File

@@ -0,0 +1,53 @@
"use client";
import { redirect } from "react-router";
import type { ClientLoaderFunctionArgs } from "react-router";
// plane imports
import { SitesProjectPublishService } from "@plane/services";
import type { TProjectPublishSettings } from "@plane/types";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
const publishService = new SitesProjectPublishService();
export const clientLoader = async ({ params, request }: ClientLoaderFunctionArgs) => {
const { workspaceSlug, projectId } = params;
// Validate required params
if (!workspaceSlug || !projectId) {
throw redirect("/404");
}
// Extract query params from the request URL
const url = new URL(request.url);
const board = url.searchParams.get("board");
const peekId = url.searchParams.get("peekId");
let response: TProjectPublishSettings | undefined = undefined;
try {
response = await publishService.retrieveSettingsByProjectId(workspaceSlug, projectId);
} catch {
throw redirect("/404");
}
if (response?.entity_name === "project") {
let redirectUrl = `/issues/${response?.anchor}`;
const urlParams = new URLSearchParams();
if (board) urlParams.append("board", String(board));
if (peekId) urlParams.append("peekId", String(peekId));
if (urlParams.toString()) redirectUrl += `?${urlParams.toString()}`;
throw redirect(redirectUrl);
} else {
throw redirect("/404");
}
};
export default function IssuesPage() {
return (
<div className="flex h-screen min-h-[500px] w-full justify-center items-center">
<LogoSpinner />
</div>
);
}

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 466 B

View File

Before

Width:  |  Height:  |  Size: 761 B

After

Width:  |  Height:  |  Size: 761 B

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 954 KiB

After

Width:  |  Height:  |  Size: 954 KiB

View File

Before

Width:  |  Height:  |  Size: 418 KiB

After

Width:  |  Height:  |  Size: 418 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

View File

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 438 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 742 B

After

Width:  |  Height:  |  Size: 742 B

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,33 @@
/**
* Ensures that a URL has a trailing slash while preserving query parameters and fragments
* @param url - The URL to process
* @returns The URL with a trailing slash added to the pathname (if not already present)
*/
export function ensureTrailingSlash(url: string): string {
try {
// Handle relative URLs by creating a URL object with a dummy base
const urlObj = new URL(url, "http://dummy.com");
// Don't modify root path
if (urlObj.pathname === "/") {
return url;
}
// Add trailing slash if it doesn't exist
if (!urlObj.pathname.endsWith("/")) {
urlObj.pathname += "/";
}
// For relative URLs, return just the path + search + hash
if (url.startsWith("/")) {
return urlObj.pathname + urlObj.search + urlObj.hash;
}
// For absolute URLs, return the full URL
return urlObj.toString();
} catch (error) {
// If URL parsing fails, return the original URL
console.warn("Failed to parse URL for trailing slash enforcement:", url, error);
return url;
}
}

View File

@@ -0,0 +1,14 @@
"use client";
import React from "react";
// Minimal shim so code using next/image compiles under React Router + Vite
// without changing call sites. It just renders a native img.
type NextImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
src: string;
};
const Image: React.FC<NextImageProps> = ({ src, alt = "", ...rest }) => <img src={src} alt={alt} {...rest} />;
export default Image;

View File

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

View File

@@ -0,0 +1,38 @@
"use client";
import { useMemo } from "react";
import { useLocation, useNavigate, useParams as useParamsRR, useSearchParams as useSearchParamsRR } from "react-router";
import { ensureTrailingSlash } from "./helper";
export function useRouter() {
const navigate = useNavigate();
return useMemo(
() => ({
push: (to: string) => navigate(ensureTrailingSlash(to)),
replace: (to: string) => navigate(ensureTrailingSlash(to), { replace: true }),
back: () => navigate(-1),
forward: () => navigate(1),
refresh: () => {
location.reload();
},
prefetch: async (_to: string) => {
// no-op in this shim
},
}),
[navigate]
);
}
export function usePathname(): string {
const { pathname } = useLocation();
return pathname;
}
export function useSearchParams(): URLSearchParams {
const [searchParams] = useSearchParamsRR();
return searchParams;
}
export function useParams() {
return useParamsRR();
}

View File

@@ -1,6 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { Outlet } from "react-router";
import useSWR from "swr";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
@@ -10,14 +11,10 @@ import { IssuesNavbarRoot } from "@/components/issues/navbar";
// hooks
import { usePublish, usePublishList } from "@/hooks/store/publish";
import { useIssueFilter } from "@/hooks/store/use-issue-filter";
import type { Route } from "./+types/client-layout";
type Props = {
children: React.ReactNode;
anchor: string;
};
export const IssuesClientLayout = observer((props: Props) => {
const { children, anchor } = props;
const IssuesClientLayout = observer((props: Route.ComponentProps) => {
const { anchor } = props.params;
// store hooks
const { fetchPublishSettings } = usePublishList();
const publishSettings = usePublish(anchor);
@@ -57,9 +54,13 @@ export const IssuesClientLayout = observer((props: Props) => {
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
<IssuesNavbarRoot publishSettings={publishSettings} />
</div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">
<Outlet />
</div>
</div>
<PoweredBy />
</>
);
});
export default IssuesClientLayout;

View File

@@ -1,7 +1,5 @@
"use server";
import { IssuesClientLayout } from "./client-layout";
type Props = {
children: React.ReactNode;
params: {
@@ -9,6 +7,7 @@ type Props = {
};
};
// TODO: Convert into SSR in order to generate metadata
export async function generateMetadata({ params }: Props) {
const { anchor } = params;
const DEFAULT_TITLE = "Plane";
@@ -49,9 +48,7 @@ export async function generateMetadata({ params }: Props) {
}
}
export default async function IssuesLayout(props: Props) {
const { children, params } = props;
const { anchor } = params;
return <IssuesClientLayout anchor={anchor}>{children}</IssuesClientLayout>;
export default async function IssuesLayout(_props: Props) {
// return <IssuesClientLayout params={{ anchor }}>{children}</IssuesClientLayout>;
return null;
}

View File

@@ -1,7 +1,7 @@
"use client";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
import { useParams, useSearchParams } from "next/navigation";
import useSWR from "swr";
// components
import { IssuesLayoutsRoot } from "@/components/issues/issue-layouts";
@@ -10,16 +10,10 @@ import { usePublish } from "@/hooks/store/publish";
import { useLabel } from "@/hooks/store/use-label";
import { useStates } from "@/hooks/store/use-state";
type Props = {
params: {
anchor: string;
};
};
const IssuesPage = observer((props: Props) => {
const { params } = props;
const { anchor } = params;
const IssuesPage = observer(() => {
// params
const params = useParams<{ anchor: string }>();
const { anchor } = params;
const searchParams = useSearchParams();
const peekId = searchParams.get("peekId") || undefined;
// store

View File

@@ -1,43 +0,0 @@
import type { Metadata } from "next";
// helpers
import { SPACE_BASE_PATH } from "@plane/constants";
// styles
import "@/styles/globals.css";
// components
import { AppProvider } from "./provider";
export const metadata: Metadata = {
title: "Plane Publish | Make your Plane boards public with one-click",
description: "Plane Publish is a customer feedback management tool built on top of plane.so",
openGraph: {
title: "Plane Publish | Make your Plane boards public with one-click",
description: "Plane Publish is a customer feedback management tool built on top of plane.so",
url: "https://sites.plane.so/",
},
keywords:
"software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration",
twitter: {
site: "@planepowers",
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<link rel="apple-touch-icon" sizes="180x180" href={`${SPACE_BASE_PATH}/favicon/apple-touch-icon.png`} />
<link rel="icon" type="image/png" sizes="32x32" href={`${SPACE_BASE_PATH}/favicon/favicon-32x32.png`} />
<link rel="icon" type="image/png" sizes="16x16" href={`${SPACE_BASE_PATH}/favicon/favicon-16x16.png`} />
<link rel="manifest" href={`${SPACE_BASE_PATH}/site.webmanifest.json`} />
<link rel="shortcut icon" href={`${SPACE_BASE_PATH}/favicon/favicon.ico`} />
<meta name="robots" content="noindex, nofollow" />
</head>
<body>
<div id="editor-portal" />
<AppProvider>
<>{children}</>
</AppProvider>
</body>
</html>
);
}

View File

@@ -2,7 +2,7 @@
import Image from "next/image";
// assets
import SomethingWentWrongImage from "public/something-went-wrong.svg";
import SomethingWentWrongImage from "@/app/assets/something-went-wrong.svg?url";
const NotFound = () => (
<div className="h-screen w-screen grid place-items-center">

View File

@@ -1,23 +1,18 @@
"use client";
import type { ReactNode, FC } from "react";
import { ThemeProvider } from "next-themes";
// components
import { TranslationProvider } from "@plane/i18n";
import { AppProgressBar } from "@/lib/b-progress";
import { InstanceProvider } from "@/lib/instance-provider";
import { StoreProvider } from "@/lib/store-provider";
import { ToastProvider } from "@/lib/toast-provider";
interface IAppProvider {
children: ReactNode;
}
export const AppProvider: FC<IAppProvider> = (props) => {
const { children } = props;
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<StoreProvider>
<AppProgressBar />
<TranslationProvider>
<ToastProvider>
<InstanceProvider>{children}</InstanceProvider>
@@ -26,4 +21,4 @@ export const AppProvider: FC<IAppProvider> = (props) => {
</StoreProvider>
</ThemeProvider>
);
};
}

66
apps/space/app/root.tsx Normal file
View File

@@ -0,0 +1,66 @@
import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router";
// styles
import "@/styles/globals.css";
// assets
import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url";
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
import faviconIco from "@/app/assets/favicon/favicon.ico?url";
// types
import type { Route } from "./+types/root";
// local imports
import ErrorPage from "./error";
import { AppProviders } from "./providers";
const APP_TITLE = "Plane Publish | Make your Plane boards public with one-click";
const APP_DESCRIPTION = "Plane Publish is a customer feedback management tool built on top of plane.so";
export const links: LinksFunction = () => [
{ rel: "apple-touch-icon", sizes: "180x180", href: appleTouchIcon },
{ rel: "icon", type: "image/png", sizes: "32x32", href: favicon32 },
{ rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 },
{ rel: "shortcut icon", href: faviconIco },
{ rel: "manifest", href: `/site.webmanifest.json` },
];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="noindex, nofollow" />
<Meta />
<Links />
</head>
<body>
<div id="editor-portal" />
<AppProviders>{children}</AppProviders>
<Scripts />
</body>
</html>
);
}
export const meta: Route.MetaFunction = () => [
{ title: APP_TITLE },
{ name: "description", content: APP_DESCRIPTION },
{ property: "og:title", content: APP_TITLE },
{ property: "og:description", content: APP_DESCRIPTION },
{ property: "og:url", content: "https://sites.plane.so/" },
{
name: "keywords",
content:
"software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration",
},
{ name: "twitter:site", content: "@planepowers" },
];
export default function Root() {
return <Outlet />;
}
export function ErrorBoundary() {
return <ErrorPage />;
}

10
apps/space/app/routes.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { RouteConfig } from "@react-router/dev/routes";
import { index, layout, route } from "@react-router/dev/routes";
export default [
index("./page.tsx"),
route(":workspaceSlug/:projectId", "./[workspaceSlug]/[projectId]/page.tsx"),
layout("./issues/[anchor]/client-layout.tsx", [route("issues/:anchor", "./issues/[anchor]/page.tsx")]),
// Catch-all route for 404 handling
route("*", "./not-found.tsx"),
] satisfies RouteConfig;

10
apps/space/app/types/next-image.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare module "next/image" {
import type { FC, ImgHTMLAttributes } from "react";
type NextImageProps = ImgHTMLAttributes<HTMLImageElement> & {
src: string;
};
const Image: FC<NextImageProps>;
export default Image;
}

14
apps/space/app/types/next-link.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
declare module "next/link" {
import type { FC, ComponentProps } from "react";
type NextLinkProps = ComponentProps<"a"> & {
href: string;
replace?: boolean;
prefetch?: boolean;
scroll?: boolean;
shallow?: boolean;
};
const Link: FC<NextLinkProps>;
export default Link;
}

View File

@@ -0,0 +1,14 @@
declare module "next/navigation" {
export function useRouter(): {
push: (url: string) => void;
replace: (url: string) => void;
back: () => void;
forward: () => void;
refresh: () => void;
prefetch: (url: string) => Promise<void>;
};
export function usePathname(): string;
export function useSearchParams(): URLSearchParams;
export function useParams<T = Record<string, string>>(): T;
}

View File

@@ -0,0 +1,5 @@
declare module "virtual:react-router/server-build" {
import type { ServerBuild } from "react-router";
const serverBuild: ServerBuild;
export default serverBuild;
}

View File

@@ -1,65 +0,0 @@
"use client";
import { observer } from "mobx-react";
import useSWR from "swr";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
import { PoweredBy } from "@/components/common/powered-by";
import { SomethingWentWrongError } from "@/components/issues/issue-layouts/error";
// hooks
import { usePublish, usePublishList } from "@/hooks/store/publish";
// Plane web
import { ViewNavbarRoot } from "@/plane-web/components/navbar";
import { useView } from "@/plane-web/hooks/store";
type Props = {
children: React.ReactNode;
params: {
anchor: string;
};
};
const ViewsLayout = observer((props: Props) => {
const { children, params } = props;
// params
const { anchor } = params;
// store hooks
const { fetchPublishSettings } = usePublishList();
const { viewData, fetchViewDetails } = useView();
const publishSettings = usePublish(anchor);
// fetch publish settings && view details
const { error } = useSWR(
anchor ? `PUBLISHED_VIEW_SETTINGS_${anchor}` : null,
anchor
? async () => {
const promises = [];
promises.push(fetchPublishSettings(anchor));
promises.push(fetchViewDetails(anchor));
await Promise.all(promises);
}
: null
);
if (error) return <SomethingWentWrongError />;
if (!publishSettings || !viewData) {
return (
<div className="flex items-center justify-center h-screen w-full">
<LogoSpinner />
</div>
);
}
return (
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
<ViewNavbarRoot publishSettings={publishSettings} />
</div>
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
<PoweredBy />
</div>
);
});
export default ViewsLayout;

View File

@@ -1,37 +0,0 @@
"use client";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
// components
import { PoweredBy } from "@/components/common/powered-by";
// hooks
import { usePublish } from "@/hooks/store/publish";
// plane-web
import { ViewLayoutsRoot } from "@/plane-web/components/issue-layouts/root";
type Props = {
params: {
anchor: string;
};
};
const ViewsPage = observer((props: Props) => {
const { params } = props;
const { anchor } = params;
// params
const searchParams = useSearchParams();
const peekId = searchParams.get("peekId") || undefined;
const publishSettings = usePublish(anchor);
if (!publishSettings) return null;
return (
<>
<ViewLayoutsRoot peekId={peekId} publishSettings={publishSettings} />
<PoweredBy />
</>
);
});
export default ViewsPage;

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { Info } from "lucide-react";
import { CloseIcon } from "@plane/propel/icons";
// helpers
@@ -11,7 +10,7 @@ type TAuthBanner = {
handleBannerData?: (bannerData: TAuthErrorInfo | undefined) => void;
};
export const AuthBanner: FC<TAuthBanner> = (props) => {
export const AuthBanner: React.FC<TAuthBanner> = (props) => {
const { bannerData, handleBannerData } = props;
if (!bannerData) return <></>;

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
// helpers
import { EAuthModes } from "@/types/auth";
@@ -28,7 +27,7 @@ const Titles: TAuthHeaderDetails = {
},
};
export const AuthHeader: FC<TAuthHeader> = (props) => {
export const AuthHeader: React.FC<TAuthHeader> = (props) => {
const { authMode } = props;
const getHeaderSubHeader = (mode: EAuthModes | null): TAuthHeaderContent => {

View File

@@ -1,7 +1,6 @@
"use client";
import type { FC } from "react";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import { useSearchParams } from "next/navigation";
@@ -11,7 +10,12 @@ import { API_BASE_URL } from "@plane/constants";
import { SitesAuthService } from "@plane/services";
import type { IEmailCheckData } from "@plane/types";
import { OAuthOptions } from "@plane/ui";
// components
// assets
import GiteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
import GithubLightLogo from "@/app/assets/logos/github-black.png?url";
import GithubDarkLogo from "@/app/assets/logos/github-dark.svg?url";
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
// helpers
import type { TAuthErrorInfo } from "@/helpers/authentication.helper";
import { EErrorAlertType, authErrorHandler, EAuthenticationErrorCodes } from "@/helpers/authentication.helper";
@@ -19,12 +23,6 @@ import { EErrorAlertType, authErrorHandler, EAuthenticationErrorCodes } from "@/
import { useInstance } from "@/hooks/store/use-instance";
// types
import { EAuthModes, EAuthSteps } from "@/types/auth";
// assets
import GithubLightLogo from "/public/logos/github-black.png";
import GithubDarkLogo from "/public/logos/github-dark.svg";
import GitlabLogo from "/public/logos/gitlab-logo.svg";
import GoogleLogo from "/public/logos/google-logo.svg";
import GiteaLogo from "/public/logos/gitea-logo.svg";
// local imports
import { TermsAndConditions } from "../terms-and-conditions";
import { AuthBanner } from "./auth-banner";
@@ -35,7 +33,7 @@ import { AuthUniqueCodeForm } from "./unique-code";
const authService = new SitesAuthService();
export const AuthRoot: FC = observer(() => {
export const AuthRoot: React.FC = observer(() => {
// router params
const searchParams = useSearchParams();
const emailParam = searchParams.get("email") || undefined;

View File

@@ -1,6 +1,6 @@
"use client";
import type { FC, FormEvent } from "react";
import type { FormEvent } from "react";
import { useMemo, useRef, useState } from "react";
import { observer } from "mobx-react";
// icons
@@ -19,7 +19,7 @@ type TAuthEmailForm = {
onSubmit: (data: IEmailCheckData) => Promise<void>;
};
export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
export const AuthEmailForm: React.FC<TAuthEmailForm> = observer((props) => {
const { onSubmit, defaultEmail } = props;
// states
const [isSubmitting, setIsSubmitting] = useState(false);

View File

@@ -1,14 +1,12 @@
"use client";
import type { FC } from "react";
import React from "react";
import Link from "next/link";
type Props = {
isSignUp?: boolean;
};
export const TermsAndConditions: FC<Props> = (props) => {
export const TermsAndConditions: React.FC<Props> = (props) => {
const { isSignUp = false } = props;
return (
<span className="flex items-center justify-center py-6">

View File

@@ -3,13 +3,13 @@
import { observer } from "mobx-react";
import Image from "next/image";
import { PlaneLockup } from "@plane/propel/icons";
// assets
import UserLoggedInImage from "@/app/assets/user-logged-in.svg?url";
// components
import { PoweredBy } from "@/components/common/powered-by";
import { UserAvatar } from "@/components/issues/navbar/user-avatar";
// hooks
import { useUser } from "@/hooks/store/use-user";
// assets
import UserLoggedInImage from "@/public/user-logged-in.svg";
export const UserLoggedIn = observer(() => {
// store hooks

View File

@@ -2,8 +2,8 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// assets
import LogoSpinnerDark from "@/public/images/logo-spinner-dark.gif";
import LogoSpinnerLight from "@/public/images/logo-spinner-light.gif";
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
export const LogoSpinner = () => {
const { resolvedTheme } = useTheme();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { WEBSITE_URL } from "@plane/constants";
// assets
import { PlaneLogo } from "@plane/propel/icons";
@@ -9,7 +8,7 @@ type TPoweredBy = {
disabled?: boolean;
};
export const PoweredBy: FC<TPoweredBy> = (props) => {
export const PoweredBy: React.FC<TPoweredBy> = (props) => {
// props
const { disabled = false } = props;

View File

@@ -1,14 +1,13 @@
"use client";
import type { FC } from "react";
import Image from "next/image";
import { useTheme } from "next-themes";
import { Button } from "@plane/propel/button";
// assets
import InstanceFailureDarkImage from "public/instance/instance-failure-dark.svg";
import InstanceFailureImage from "public/instance/instance-failure.svg";
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url";
export const InstanceFailureView: FC = () => {
export const InstanceFailureView: React.FC = () => {
const { resolvedTheme } = useTheme();
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useCallback } from "react";
import { cloneDeep } from "lodash-es";
import { observer } from "mobx-react";
@@ -16,7 +15,7 @@ type TIssueAppliedFilters = {
anchor: string;
};
export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) => {
export const IssueAppliedFilters: React.FC<TIssueAppliedFilters> = observer((props) => {
const { anchor } = props;
// router
const router = useRouter();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useCallback } from "react";
import { cloneDeep } from "lodash-es";
import { observer } from "mobx-react";
@@ -21,7 +20,7 @@ type IssueFiltersDropdownProps = {
anchor: string;
};
export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((props) => {
export const IssueFiltersDropdown: React.FC<IssueFiltersDropdownProps> = observer((props) => {
const { anchor } = props;
// router
const router = useRouter();

View File

@@ -1,6 +1,6 @@
import Image from "next/image";
// assets
import SomethingWentWrongImage from "public/something-went-wrong.svg";
import SomethingWentWrongImage from "@/app/assets/something-went-wrong.svg?url";
export const SomethingWentWrongError = () => (
<div className="grid min-h-screen w-full place-items-center p-6">

View File

@@ -1,7 +1,5 @@
"use client";
import type { FC } from "react";
import React from "react";
import { observer } from "mobx-react";
import { Circle } from "lucide-react";
// types
@@ -14,7 +12,7 @@ interface IHeaderGroupByCard {
count: number;
}
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
export const HeaderGroupByCard: React.FC<IHeaderGroupByCard> = observer((props) => {
const { icon, title, count } = props;
return (

View File

@@ -1,5 +1,3 @@
import type { FC } from "react";
import React from "react";
import { observer } from "mobx-react";
import { Circle } from "lucide-react";
import { ChevronDownIcon, ChevronUpIcon } from "@plane/propel/icons";
@@ -13,7 +11,7 @@ interface IHeaderSubGroupByCard {
toggleExpanded: () => void;
}
export const HeaderSubGroupByCard: FC<IHeaderSubGroupByCard> = observer((props) => {
export const HeaderSubGroupByCard: React.FC<IHeaderSubGroupByCard> = observer((props) => {
const { icon, title, count, isExpanded, toggleExpanded } = props;
return (
<div

View File

@@ -1,4 +1,4 @@
import type { FC, MutableRefObject } from "react";
import type { MutableRefObject } from "react";
// types
import type { IIssueDisplayProperties } from "@plane/types";
import { IssueBlock } from "./block";
@@ -10,7 +10,7 @@ interface Props {
containerRef: MutableRefObject<HTMLDivElement | null>;
}
export const IssueBlocksList: FC<Props> = (props) => {
export const IssueBlocksList: React.FC<Props> = (props) => {
const { issueIds = [], groupId, displayProperties } = props;
return (

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useEffect } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
@@ -23,7 +22,7 @@ type Props = {
publishSettings: PublishStore;
};
export const IssuesLayoutsRoot: FC<Props> = observer((props) => {
export const IssuesLayoutsRoot: React.FC<Props> = observer((props) => {
const { peekId, publishSettings } = props;
// store hooks
const { getIssueFilters } = useIssueFilter();

View File

@@ -1,4 +1,3 @@
import type { ReactNode } from "react";
import { observer } from "mobx-react";
// plane imports
import type { IIssueDisplayProperties } from "@plane/types";
@@ -7,7 +6,7 @@ interface IWithDisplayPropertiesHOC {
displayProperties: IIssueDisplayProperties;
shouldRenderProperty?: (displayProperties: IIssueDisplayProperties) => boolean;
displayPropertyKey: keyof IIssueDisplayProperties | (keyof IIssueDisplayProperties)[];
children: ReactNode;
children: React.ReactNode;
}
export const WithDisplayPropertiesHOC = observer(

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter, useSearchParams } from "next/navigation";
@@ -25,7 +24,7 @@ export type NavbarControlsProps = {
publishSettings: PublishStore;
};
export const NavbarControls: FC<NavbarControlsProps> = observer((props) => {
export const NavbarControls: React.FC<NavbarControlsProps> = observer((props) => {
// props
const { publishSettings } = props;
// router

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
import { useRouter, useSearchParams } from "next/navigation";
// ui
@@ -20,7 +19,7 @@ type Props = {
anchor: string;
};
export const IssuesLayoutSelection: FC<Props> = observer((props) => {
export const IssuesLayoutSelection: React.FC<Props> = observer((props) => {
const { anchor } = props;
// hooks
const { t } = useTranslation();

View File

@@ -1,6 +1,5 @@
"use client";
import type { FC } from "react";
import { observer } from "mobx-react";
import { ProjectIcon } from "@plane/propel/icons";
// components
@@ -14,7 +13,7 @@ type Props = {
publishSettings: PublishStore;
};
export const IssuesNavbarRoot: FC<Props> = observer((props) => {
export const IssuesNavbarRoot: React.FC<Props> = observer((props) => {
const { publishSettings } = props;
// hooks
const { project_details } = publishSettings;

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