[WEB-5040] feat: admin react-router migration (#7922)
@@ -66,4 +66,4 @@ temp/
|
||||
.react-router/
|
||||
build/
|
||||
node_modules/
|
||||
README.md
|
||||
README.md
|
||||
|
||||
3
.gitignore
vendored
@@ -102,5 +102,8 @@ dev-editor
|
||||
storybook-static
|
||||
|
||||
CLAUDE.md
|
||||
|
||||
build/
|
||||
.react-router/
|
||||
AGENTS.md
|
||||
temp/
|
||||
|
||||
5
apps/admin/.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
# React Router - https://github.com/remix-run/react-router-templates/blob/dc79b1a065f59f3bfd840d4ef75cc27689b611e6/default/.dockerignore
|
||||
.react-router/
|
||||
build/
|
||||
node_modules/
|
||||
README.md
|
||||
@@ -1,6 +1,7 @@
|
||||
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 }],
|
||||
@@ -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 admin app
|
||||
RUN turbo prune --scope=admin --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 admin package
|
||||
RUN pnpm turbo run build --filter=admin
|
||||
|
||||
# *****************************************************************************
|
||||
# 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/admin/.next/standalone ./
|
||||
COPY --from=installer /app/apps/admin/.next/static ./apps/admin/.next/static
|
||||
COPY --from=installer /app/apps/admin/public ./apps/admin/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/admin/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=installer /app/apps/admin/build/client /usr/share/nginx/html/god-mode
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "apps/admin/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;"]
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Artificial Intelligence Settings - God Mode",
|
||||
};
|
||||
|
||||
export default function AILayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import { Loader } from "@plane/ui";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceAIForm } from "./form";
|
||||
|
||||
const InstanceAIPage = observer(() => {
|
||||
const InstanceAIPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig } = useInstance();
|
||||
|
||||
@@ -42,4 +43,6 @@ const InstanceAIPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Artificial Intelligence Settings - God Mode" }];
|
||||
|
||||
export default InstanceAIPage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Gitea Authentication - God Mode",
|
||||
};
|
||||
|
||||
export default function GiteaAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -3,18 +3,17 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane internal packages
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
// components
|
||||
import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// icons
|
||||
import giteaLogo from "@/public/logos/gitea-logo.svg";
|
||||
//local components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGiteaConfigForm } from "./form";
|
||||
|
||||
const InstanceGiteaAuthenticationPage = observer(() => {
|
||||
@@ -22,8 +21,6 @@ const InstanceGiteaAuthenticationPage = observer(() => {
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
// theme
|
||||
const { resolvedTheme } = useTheme();
|
||||
// config
|
||||
const enableGiteaConfig = formattedConfig?.IS_GITEA_ENABLED ?? "";
|
||||
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
|
||||
@@ -100,5 +97,6 @@ const InstanceGiteaAuthenticationPage = observer(() => {
|
||||
</>
|
||||
);
|
||||
});
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Gitea Authentication - God Mode" }];
|
||||
|
||||
export default InstanceGiteaAuthenticationPage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "GitHub Authentication - God Mode",
|
||||
};
|
||||
|
||||
export default function GitHubAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -10,16 +10,17 @@ import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
|
||||
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// icons
|
||||
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||
// local components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGithubConfigForm } from "./form";
|
||||
|
||||
const InstanceGithubAuthenticationPage = observer(() => {
|
||||
const InstanceGithubAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -111,4 +112,6 @@ const InstanceGithubAuthenticationPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "GitHub Authentication - God Mode" }];
|
||||
|
||||
export default InstanceGithubAuthenticationPage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "GitLab Authentication - God Mode",
|
||||
};
|
||||
|
||||
export default function GitlabAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -7,15 +7,16 @@ import useSWR from "swr";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
// components
|
||||
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// icons
|
||||
import GitlabLogo from "@/public/logos/gitlab-logo.svg";
|
||||
// local components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGitlabConfigForm } from "./form";
|
||||
|
||||
const InstanceGitlabAuthenticationPage = observer(() => {
|
||||
const InstanceGitlabAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -99,4 +100,6 @@ const InstanceGitlabAuthenticationPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "GitLab Authentication - God Mode" }];
|
||||
|
||||
export default InstanceGitlabAuthenticationPage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Google Authentication - God Mode",
|
||||
};
|
||||
|
||||
export default function GoogleAuthenticationLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -7,15 +7,16 @@ import useSWR from "swr";
|
||||
import { setPromiseToast } from "@plane/propel/toast";
|
||||
import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
// components
|
||||
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// icons
|
||||
import GoogleLogo from "@/public/logos/google-logo.svg";
|
||||
// local components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceGoogleConfigForm } from "./form";
|
||||
|
||||
const InstanceGoogleAuthenticationPage = observer(() => {
|
||||
const InstanceGoogleAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
// state
|
||||
@@ -100,4 +101,6 @@ const InstanceGoogleAuthenticationPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Google Authentication - God Mode" }];
|
||||
|
||||
export default InstanceGoogleAuthenticationPage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Authentication Settings - Plane Web",
|
||||
};
|
||||
|
||||
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -12,8 +12,9 @@ import { cn } from "@plane/utils";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// plane admin components
|
||||
import { AuthenticationModes } from "@/plane-admin/components/authentication";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const InstanceAuthenticationPage = observer(() => {
|
||||
const InstanceAuthenticationPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
|
||||
|
||||
@@ -111,4 +112,6 @@ const InstanceAuthenticationPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Authentication Settings - Plane Web" }];
|
||||
|
||||
export default InstanceAuthenticationPage;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
interface EmailLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Email Settings - God Mode",
|
||||
};
|
||||
|
||||
export default function EmailLayout({ children }: EmailLayoutProps) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -8,9 +8,10 @@ import { Loader, ToggleSwitch } from "@plane/ui";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceEmailForm } from "./email-config-form";
|
||||
|
||||
const InstanceEmailPage: React.FC = observer(() => {
|
||||
const InstanceEmailPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { fetchInstanceConfigurations, formattedConfig, disableEmail } = useInstance();
|
||||
|
||||
@@ -91,4 +92,6 @@ const InstanceEmailPage: React.FC = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Email Settings - God Mode" }];
|
||||
|
||||
export default InstanceEmailPage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "General Settings - God Mode",
|
||||
};
|
||||
|
||||
export default function GeneralLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import type { Route } from "./+types/page";
|
||||
import { GeneralConfigurationForm } from "./form";
|
||||
|
||||
function GeneralPage() {
|
||||
@@ -28,4 +29,6 @@ function GeneralPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "General Settings - God Mode" }];
|
||||
|
||||
export default observer(GeneralPage);
|
||||
|
||||
@@ -44,6 +44,8 @@ export const AdminHeader: FC = observer(() => {
|
||||
return "GitHub";
|
||||
case "gitlab":
|
||||
return "GitLab";
|
||||
case "gitea":
|
||||
return "Gitea";
|
||||
case "workspace":
|
||||
return "Workspace";
|
||||
case "create":
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
interface ImageLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Images Settings - God Mode",
|
||||
};
|
||||
|
||||
export default function ImageLayout({ children }: ImageLayoutProps) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import { Loader } from "@plane/ui";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// local
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceImageConfigForm } from "./form";
|
||||
|
||||
const InstanceImagePage = observer(() => {
|
||||
const InstanceImagePage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// store
|
||||
const { formattedConfig, fetchInstanceConfigurations } = useInstance();
|
||||
|
||||
@@ -38,4 +39,6 @@ const InstanceImagePage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Images Settings - God Mode" }];
|
||||
|
||||
export default InstanceImagePage;
|
||||
|
||||
@@ -1,34 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { NewUserPopup } from "@/components/new-user-popup";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
// local components
|
||||
import type { Route } from "./+types/layout";
|
||||
import { AdminHeader } from "./header";
|
||||
import { AdminSidebar } from "./sidebar";
|
||||
|
||||
type TAdminLayout = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const AdminLayout: FC<TAdminLayout> = (props) => {
|
||||
const { children } = props;
|
||||
const AdminLayout: React.FC<Route.ComponentProps> = () => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { replace } = useRouter();
|
||||
// store hooks
|
||||
const { isUserLoggedIn } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (isUserLoggedIn === false) {
|
||||
router.push("/");
|
||||
}
|
||||
}, [router, isUserLoggedIn]);
|
||||
if (isUserLoggedIn === false) replace("/");
|
||||
}, [replace, isUserLoggedIn]);
|
||||
|
||||
if (isUserLoggedIn === undefined) {
|
||||
return (
|
||||
@@ -44,7 +38,9 @@ const AdminLayout: FC<TAdminLayout> = (props) => {
|
||||
<AdminSidebar />
|
||||
<main className="relative flex h-full w-full flex-col overflow-hidden bg-custom-background-100">
|
||||
<AdminHeader />
|
||||
<div className="h-full w-full overflow-hidden">{children}</div>
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
<NewUserPopup />
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import type { Route } from "./+types/page";
|
||||
import { WorkspaceCreateForm } from "./form";
|
||||
|
||||
const WorkspaceCreatePage = observer(() => (
|
||||
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>
|
||||
@@ -18,4 +19,6 @@ const WorkspaceCreatePage = observer(() => (
|
||||
</div>
|
||||
));
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Create Workspace - God Mode" }];
|
||||
|
||||
export default WorkspaceCreatePage;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Workspace Management - God Mode",
|
||||
};
|
||||
|
||||
export default function WorkspaceManagementLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -16,8 +16,9 @@ import { cn } from "@plane/utils";
|
||||
import { WorkspaceListItem } from "@/components/workspace/list-item";
|
||||
// hooks
|
||||
import { useInstance, useWorkspace } from "@/hooks/store";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
const WorkspaceManagementPage = observer(() => {
|
||||
const WorkspaceManagementPage = observer<React.FC<Route.ComponentProps>>(() => {
|
||||
// states
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
// store
|
||||
@@ -167,4 +168,6 @@ const WorkspaceManagementPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const meta: Route.MetaFunction = () => [{ title: "Workspace Management - God Mode" }];
|
||||
|
||||
export default WorkspaceManagementPage;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ReactNode } from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { KeyRound, Mails } from "lucide-react";
|
||||
@@ -8,16 +7,16 @@ import { SUPPORT_EMAIL, EAdminAuthErrorCodes } from "@plane/constants";
|
||||
import type { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
|
||||
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
|
||||
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
||||
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
|
||||
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
|
||||
import { GithubConfiguration } from "@/components/authentication/github-config";
|
||||
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
|
||||
import { GoogleConfiguration } from "@/components/authentication/google-config";
|
||||
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
|
||||
// images
|
||||
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||
import GitlabLogo from "@/public/logos/gitlab-logo.svg";
|
||||
import GoogleLogo from "@/public/logos/google-logo.svg";
|
||||
|
||||
export enum EErrorAlertType {
|
||||
BANNER_ALERT = "BANNER_ALERT",
|
||||
@@ -28,7 +27,7 @@ export enum EErrorAlertType {
|
||||
}
|
||||
|
||||
const errorCodeMessages: {
|
||||
[key in EAdminAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
|
||||
[key in EAdminAuthErrorCodes]: { title: string; message: (email?: string | undefined) => React.ReactNode };
|
||||
} = {
|
||||
// admin
|
||||
[EAdminAuthErrorCodes.ADMIN_ALREADY_EXIST]: {
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
"use client";
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Outlet } from "react-router";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/use-user";
|
||||
|
||||
function RootLayout() {
|
||||
// router
|
||||
const { replace } = useRouter();
|
||||
// store hooks
|
||||
const { isUserLoggedIn } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (isUserLoggedIn === true) replace("/general");
|
||||
}, [replace, isUserLoggedIn]);
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex flex-col items-center w-screen h-screen overflow-hidden overflow-y-auto pt-6 pb-10 px-8">
|
||||
{children}
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(RootLayout);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { InstanceSetupForm } from "@/components/instance/setup-form";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import type { Route } from "./+types/page";
|
||||
import { InstanceSignInForm } from "./sign-in-form";
|
||||
|
||||
const HomePage = () => {
|
||||
@@ -38,3 +39,8 @@ const HomePage = () => {
|
||||
};
|
||||
|
||||
export default observer(HomePage);
|
||||
|
||||
export const meta: Route.MetaFunction = () => [
|
||||
{ title: "Admin – Instance Setup & Sign-In" },
|
||||
{ name: "description", content: "Configure your Plane instance or sign in to the admin portal." },
|
||||
];
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 466 B |
|
Before Width: | Height: | Size: 761 B After Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
17
apps/admin/app/assets/images/404.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="596" height="568" viewBox="0 0 596 568" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="293" cy="284" r="284" fill="#F1F3F5"/>
|
||||
<path d="M191.016 181.117H178.242V174.008H190.293V169.477H178.242V162.68H191.016V157.816H172.344V186H191.016V181.117ZM230.172 162.426H235.191C238.121 162.426 239.957 164.184 239.957 166.918C239.957 169.711 238.219 171.41 235.25 171.41H230.172V162.426ZM230.172 175.688H234.898L240.152 186H246.832L240.895 174.809C244.137 173.539 246.012 170.414 246.012 166.801C246.012 161.234 242.301 157.816 235.816 157.816H224.273V186H230.172V175.688ZM284.797 162.426H289.816C292.746 162.426 294.582 164.184 294.582 166.918C294.582 169.711 292.844 171.41 289.875 171.41H284.797V162.426ZM284.797 175.688H289.523L294.777 186H301.457L295.52 174.809C298.762 173.539 300.637 170.414 300.637 166.801C300.637 161.234 296.926 157.816 290.441 157.816H278.898V186H284.797V175.688ZM346.238 157.328C337.879 157.328 332.645 162.934 332.645 171.918C332.645 180.883 337.879 186.488 346.238 186.488C354.578 186.488 359.832 180.883 359.832 171.918C359.832 162.934 354.578 157.328 346.238 157.328ZM346.238 162.25C350.848 162.25 353.797 166 353.797 171.918C353.797 177.816 350.848 181.547 346.238 181.547C341.609 181.547 338.66 177.816 338.66 171.918C338.66 166 341.629 162.25 346.238 162.25ZM398.539 162.426H403.559C406.488 162.426 408.324 164.184 408.324 166.918C408.324 169.711 406.586 171.41 403.617 171.41H398.539V162.426ZM398.539 175.688H403.266L408.52 186H415.199L409.262 174.809C412.504 173.539 414.379 170.414 414.379 166.801C414.379 161.234 410.668 157.816 404.184 157.816H392.641V186H398.539V175.688Z" fill="black"/>
|
||||
<path d="M191.016 181.117H178.242V174.008H190.293V169.477H178.242V162.68H191.016V157.816H172.344V186H191.016V181.117ZM230.172 162.426H235.191C238.121 162.426 239.957 164.184 239.957 166.918C239.957 169.711 238.219 171.41 235.25 171.41H230.172V162.426ZM230.172 175.688H234.898L240.152 186H246.832L240.895 174.809C244.137 173.539 246.012 170.414 246.012 166.801C246.012 161.234 242.301 157.816 235.816 157.816H224.273V186H230.172V175.688ZM284.797 162.426H289.816C292.746 162.426 294.582 164.184 294.582 166.918C294.582 169.711 292.844 171.41 289.875 171.41H284.797V162.426ZM284.797 175.688H289.523L294.777 186H301.457L295.52 174.809C298.762 173.539 300.637 170.414 300.637 166.801C300.637 161.234 296.926 157.816 290.441 157.816H278.898V186H284.797V175.688ZM346.238 157.328C337.879 157.328 332.645 162.934 332.645 171.918C332.645 180.883 337.879 186.488 346.238 186.488C354.578 186.488 359.832 180.883 359.832 171.918C359.832 162.934 354.578 157.328 346.238 157.328ZM346.238 162.25C350.848 162.25 353.797 166 353.797 171.918C353.797 177.816 350.848 181.547 346.238 181.547C341.609 181.547 338.66 177.816 338.66 171.918C338.66 166 341.629 162.25 346.238 162.25ZM398.539 162.426H403.559C406.488 162.426 408.324 164.184 408.324 166.918C408.324 169.711 406.586 171.41 403.617 171.41H398.539V162.426ZM398.539 175.688H403.266L408.52 186H415.199L409.262 174.809C412.504 173.539 414.379 170.414 414.379 166.801C414.379 161.234 410.668 157.816 404.184 157.816H392.641V186H398.539V175.688Z" fill="black"/>
|
||||
<mask id="mask0_468_2978" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="10" y="258" width="568" height="222">
|
||||
<path d="M115.723 475H157.764V437.354H185.303V401.904H157.764V263.623H95.3613C52.002 327.49 29.0039 364.551 10.5469 399.707V437.354H115.723V475ZM49.3652 402.051C66.5039 368.945 84.9609 340.088 115.723 294.971H116.602V403.223H49.3652V402.051ZM292.857 479.688C346.031 479.688 378.258 437.061 378.258 368.799C378.258 300.537 345.738 258.789 292.857 258.789C239.977 258.789 207.311 300.684 207.311 368.945C207.311 437.354 239.684 479.688 292.857 479.688ZM292.857 444.238C267.662 444.238 252.281 416.992 252.281 368.945C252.281 321.338 267.955 294.238 292.857 294.238C317.906 294.238 333.287 321.191 333.287 368.945C333.287 417.139 318.053 444.238 292.857 444.238ZM507.785 475H549.826V437.354H577.365V401.904H549.826V263.623H487.424C444.064 327.49 421.066 364.551 402.609 399.707V437.354H507.785V475ZM441.428 402.051C458.566 368.945 477.023 340.088 507.785 294.971H508.664V403.223H441.428V402.051Z" fill="#858E96"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_468_2978)">
|
||||
<path d="M369.5 527L243 223.5H-27.5V527H369.5Z" fill="#858E96"/>
|
||||
</g>
|
||||
<mask id="mask1_468_2978" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="19" y="277" width="568" height="222">
|
||||
<path d="M124.723 494H166.764V456.354H194.303V420.904H166.764V282.623H104.361C61.002 346.49 38.0039 383.551 19.5469 418.707V456.354H124.723V494ZM58.3652 421.051C75.5039 387.945 93.9609 359.088 124.723 313.971H125.602V422.223H58.3652V421.051ZM301.857 498.688C355.031 498.688 387.258 456.061 387.258 387.799C387.258 319.537 354.738 277.789 301.857 277.789C248.977 277.789 216.311 319.684 216.311 387.945C216.311 456.354 248.684 498.688 301.857 498.688ZM301.857 463.238C276.662 463.238 261.281 435.992 261.281 387.945C261.281 340.338 276.955 313.238 301.857 313.238C326.906 313.238 342.287 340.191 342.287 387.945C342.287 436.139 327.053 463.238 301.857 463.238ZM516.785 494H558.826V456.354H586.365V420.904H558.826V282.623H496.424C453.064 346.49 430.066 383.551 411.609 418.707V456.354H516.785V494ZM450.428 421.051C467.566 387.945 486.023 359.088 516.785 313.971H517.664V422.223H450.428V421.051Z" fill="#ACB5BD"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_468_2978)">
|
||||
<path d="M250 242.5L376.5 546H647V242.5H250Z" fill="#858E96"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 954 KiB After Width: | Height: | Size: 954 KiB |
|
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 418 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 742 B After Width: | Height: | Size: 742 B |
|
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 702 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
33
apps/admin/app/compat/next/helper.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
14
apps/admin/app/compat/next/image.tsx
Normal 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;
|
||||
24
apps/admin/app/compat/next/link.tsx
Normal 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;
|
||||
34
apps/admin/app/compat/next/navigation.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useLocation, useNavigate, 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;
|
||||
}
|
||||
34
apps/admin/app/components/404.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router";
|
||||
// ui
|
||||
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" />
|
||||
</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;
|
||||
@@ -1,9 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export default function RootErrorPage() {
|
||||
return (
|
||||
<div>
|
||||
<p>Something went wrong.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
// plane imports
|
||||
import { ADMIN_BASE_PATH } from "@plane/constants";
|
||||
// styles
|
||||
import "@/styles/globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||
description:
|
||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||
openGraph: {
|
||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||
description:
|
||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||
url: "https://plane.so/",
|
||||
},
|
||||
keywords:
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration",
|
||||
twitter: {
|
||||
site: "@planepowers",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
const ASSET_PREFIX = ADMIN_BASE_PATH;
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={`${ASSET_PREFIX}/favicon/apple-touch-icon.png`} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={`${ASSET_PREFIX}/favicon/favicon-32x32.png`} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={`${ASSET_PREFIX}/favicon/favicon-16x16.png`} />
|
||||
<link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} />
|
||||
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
||||
</head>
|
||||
<body className={`antialiased`}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -2,24 +2,25 @@
|
||||
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { SWRConfig } from "swr";
|
||||
// providers
|
||||
import { InstanceProvider } from "./instance.provider";
|
||||
import { StoreProvider } from "./store.provider";
|
||||
import { ToastWithTheme } from "./toast";
|
||||
import { UserProvider } from "./user.provider";
|
||||
import { AppProgressBar } from "@/lib/b-progress";
|
||||
import { InstanceProvider } from "./(all)/instance.provider";
|
||||
import { StoreProvider } from "./(all)/store.provider";
|
||||
import { ToastWithTheme } from "./(all)/toast";
|
||||
import { UserProvider } from "./(all)/user.provider";
|
||||
|
||||
const DEFAULT_SWR_CONFIG = {
|
||||
refreshWhenHidden: false,
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: true,
|
||||
refreshInterval: 600000,
|
||||
refreshInterval: 600_000,
|
||||
errorRetryCount: 3,
|
||||
};
|
||||
|
||||
export default function InstanceLayout({ children }: { children: React.ReactNode }) {
|
||||
export function AppProviders({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||
<AppProgressBar />
|
||||
<ToastWithTheme />
|
||||
<SWRConfig value={DEFAULT_SWR_CONFIG}>
|
||||
<StoreProvider>
|
||||
65
apps/admin/app/root.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Links, Meta, Outlet, Scripts } from "react-router";
|
||||
import type { LinksFunction } from "react-router";
|
||||
import "../styles/globals.css";
|
||||
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";
|
||||
import type { Route } from "./+types/root";
|
||||
import { AppProviders } from "./providers";
|
||||
|
||||
const APP_TITLE = "Plane | Simple, extensible, open-source project management tool.";
|
||||
const APP_DESCRIPTION =
|
||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.";
|
||||
|
||||
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: ReactNode }) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body className="antialiased" suppressHydrationWarning>
|
||||
<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://plane.so/" },
|
||||
{
|
||||
name: "keywords",
|
||||
content:
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration",
|
||||
},
|
||||
{ name: "twitter:site", content: "@planepowers" },
|
||||
];
|
||||
|
||||
export default function Root() {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
return (
|
||||
<div>
|
||||
<p>Something went wrong.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
apps/admin/app/routes.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { index, layout, route } from "@react-router/dev/routes";
|
||||
import type { RouteConfig } from "@react-router/dev/routes";
|
||||
|
||||
export default [
|
||||
layout("./(all)/(home)/layout.tsx", [index("./(all)/(home)/page.tsx")]),
|
||||
layout("./(all)/(dashboard)/layout.tsx", [
|
||||
route("general", "./(all)/(dashboard)/general/page.tsx"),
|
||||
route("workspace", "./(all)/(dashboard)/workspace/page.tsx"),
|
||||
route("workspace/create", "./(all)/(dashboard)/workspace/create/page.tsx"),
|
||||
route("email", "./(all)/(dashboard)/email/page.tsx"),
|
||||
route("authentication", "./(all)/(dashboard)/authentication/page.tsx"),
|
||||
route("authentication/github", "./(all)/(dashboard)/authentication/github/page.tsx"),
|
||||
route("authentication/gitlab", "./(all)/(dashboard)/authentication/gitlab/page.tsx"),
|
||||
route("authentication/google", "./(all)/(dashboard)/authentication/google/page.tsx"),
|
||||
route("authentication/gitea", "./(all)/(dashboard)/authentication/gitea/page.tsx"),
|
||||
route("ai", "./(all)/(dashboard)/ai/page.tsx"),
|
||||
route("image", "./(all)/(dashboard)/image/page.tsx"),
|
||||
]),
|
||||
// Catch-all route for 404 handling - must be last
|
||||
route("*", "./components/404.tsx"),
|
||||
] satisfies RouteConfig;
|
||||
5
apps/admin/app/types/next-image.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module "next/image" {
|
||||
type Props = React.ComponentProps<"img"> & { src: string };
|
||||
const Image: React.FC<Props>;
|
||||
export default Image;
|
||||
}
|
||||
12
apps/admin/app/types/next-link.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
declare module "next/link" {
|
||||
type Props = React.ComponentProps<"a"> & {
|
||||
href: string;
|
||||
replace?: boolean;
|
||||
prefetch?: boolean;
|
||||
scroll?: boolean;
|
||||
shallow?: boolean;
|
||||
};
|
||||
|
||||
const Link: React.FC<Props>;
|
||||
export default Link;
|
||||
}
|
||||
13
apps/admin/app/types/next-navigation.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
declare module "next/navigation" {
|
||||
export function useRouter(): {
|
||||
push: (to: string) => void;
|
||||
replace: (to: string) => void;
|
||||
back: () => void;
|
||||
forward: () => void;
|
||||
refresh: () => void;
|
||||
prefetch: (to: string) => Promise<void> | void;
|
||||
};
|
||||
|
||||
export function usePathname(): string;
|
||||
export function useSearchParams(): URLSearchParams;
|
||||
}
|
||||
5
apps/admin/app/types/react-router-virtual.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module "virtual:react-router/server-build" {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const build: any;
|
||||
export default build;
|
||||
}
|
||||
@@ -10,6 +10,13 @@ import type {
|
||||
} from "@plane/types";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// components
|
||||
import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
|
||||
import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
|
||||
import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
|
||||
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
|
||||
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
|
||||
import OIDCLogo from "@/app/assets/logos/oidc-logo.svg?url";
|
||||
import SAMLLogo from "@/app/assets/logos/saml-logo.svg?url";
|
||||
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
|
||||
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
|
||||
import { GiteaConfiguration } from "@/components/authentication/gitea-config";
|
||||
@@ -20,13 +27,6 @@ import { PasswordLoginConfiguration } from "@/components/authentication/password
|
||||
// plane admin components
|
||||
import { UpgradeButton } from "@/plane-admin/components/common";
|
||||
// assets
|
||||
import giteaLogo from "@/public/logos/gitea-logo.svg";
|
||||
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||
import GitlabLogo from "@/public/logos/gitlab-logo.svg";
|
||||
import GoogleLogo from "@/public/logos/google-logo.svg";
|
||||
import OIDCLogo from "@/public/logos/oidc-logo.svg";
|
||||
import SAMLLogo from "@/public/logos/saml-logo.svg";
|
||||
|
||||
export type TAuthenticationModeProps = {
|
||||
disabled: boolean;
|
||||
@@ -107,7 +107,7 @@ export const getAuthenticationModes: (props: TGetBaseAuthenticationModeProps) =>
|
||||
},
|
||||
];
|
||||
|
||||
export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer((props) => {
|
||||
export const AuthenticationModes = observer<React.FC<TAuthenticationModeProps>>((props) => {
|
||||
const { disabled, updateConfig } = props;
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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();
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
"use client";
|
||||
import type { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// assets
|
||||
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
|
||||
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 = observer(() => {
|
||||
export const InstanceFailureView: React.FC = observer(() => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@plane/propel/button";
|
||||
// assets
|
||||
import PlaneTakeOffImage from "@/public/images/plane-takeoff.png";
|
||||
import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url";
|
||||
|
||||
export const InstanceNotReady: FC = () => (
|
||||
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">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
// assets
|
||||
import LogoSpinnerDark from "@/public/images/logo-spinner-dark.gif";
|
||||
import LogoSpinnerLight from "@/public/images/logo-spinner-light.gif";
|
||||
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
|
||||
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
|
||||
|
||||
export const InstanceLoading = () => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTheme as nextUseTheme } from "next-themes";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
// ui
|
||||
import { Button, getButtonStyling } from "@plane/propel/button";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
// hooks
|
||||
import TakeoffIconDark from "@/app/assets/logos/takeoff-icon-dark.svg?url";
|
||||
import TakeoffIconLight from "@/app/assets/logos/takeoff-icon-light.svg?url";
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// icons
|
||||
import TakeoffIconLight from "/public/logos/takeoff-icon-light.svg";
|
||||
import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
|
||||
|
||||
export const NewUserPopup: React.FC = observer(() => {
|
||||
export const NewUserPopup = observer(() => {
|
||||
// hooks
|
||||
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
||||
// theme
|
||||
const { resolvedTheme } = nextUseTheme();
|
||||
const { resolvedTheme } = useNextTheme();
|
||||
|
||||
if (!isNewUserPopup) return <></>;
|
||||
return (
|
||||
|
||||
140
apps/admin/core/lib/b-progress/AppProgressBar.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { BProgress } from "@bprogress/core";
|
||||
import { useNavigation } from "react-router";
|
||||
import "@bprogress/core/css";
|
||||
|
||||
/**
|
||||
* Progress bar configuration options
|
||||
*/
|
||||
interface ProgressConfig {
|
||||
/** Whether to show the loading spinner */
|
||||
showSpinner: boolean;
|
||||
/** Minimum progress percentage (0-1) */
|
||||
minimum: number;
|
||||
/** Animation speed in milliseconds */
|
||||
speed: number;
|
||||
/** Auto-increment speed in milliseconds */
|
||||
trickleSpeed: number;
|
||||
/** CSS easing function */
|
||||
easing: string;
|
||||
/** Enable auto-increment */
|
||||
trickle: boolean;
|
||||
/** Delay before showing progress bar in milliseconds */
|
||||
delay: number;
|
||||
/** Whether to disable the progress bar */
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the progress bar
|
||||
*/
|
||||
const PROGRESS_CONFIG: Readonly<ProgressConfig> = {
|
||||
showSpinner: false,
|
||||
minimum: 0.1,
|
||||
speed: 400,
|
||||
trickleSpeed: 800,
|
||||
easing: "ease",
|
||||
trickle: true,
|
||||
delay: 0,
|
||||
isDisabled: import.meta.env.PROD, // Disable progress bar in production builds
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Navigation Progress Bar Component
|
||||
*
|
||||
* Automatically displays a progress bar at the top of the page during React Router navigation.
|
||||
* Integrates with React Router's useNavigation hook to monitor route changes.
|
||||
*
|
||||
* Note: Progress bar is disabled in production builds.
|
||||
*
|
||||
* @returns null - This component doesn't render any visible elements
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function App() {
|
||||
* return (
|
||||
* <>
|
||||
* <AppProgressBar />
|
||||
* <Outlet />
|
||||
* </>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function AppProgressBar(): null {
|
||||
const navigation = useNavigation();
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const startedRef = useRef<boolean>(false);
|
||||
|
||||
// Initialize BProgress once on mount
|
||||
useEffect(() => {
|
||||
// Skip initialization in production builds
|
||||
if (PROGRESS_CONFIG.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure BProgress with our settings
|
||||
BProgress.configure({
|
||||
showSpinner: PROGRESS_CONFIG.showSpinner,
|
||||
minimum: PROGRESS_CONFIG.minimum,
|
||||
speed: PROGRESS_CONFIG.speed,
|
||||
trickleSpeed: PROGRESS_CONFIG.trickleSpeed,
|
||||
easing: PROGRESS_CONFIG.easing,
|
||||
trickle: PROGRESS_CONFIG.trickle,
|
||||
});
|
||||
|
||||
// Render the progress bar element in the DOM
|
||||
BProgress.render(true);
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
if (BProgress.isStarted()) {
|
||||
BProgress.done();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle navigation state changes
|
||||
useEffect(() => {
|
||||
// Skip navigation tracking in production builds
|
||||
if (PROGRESS_CONFIG.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigation.state === "idle") {
|
||||
// Navigation complete - clear any pending timer
|
||||
if (timerRef.current !== null) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
|
||||
// Complete progress if it was started
|
||||
if (startedRef.current) {
|
||||
BProgress.done();
|
||||
startedRef.current = false;
|
||||
}
|
||||
} else {
|
||||
// Navigation in progress (loading or submitting)
|
||||
// Only start if not already started and no timer pending
|
||||
if (timerRef.current === null && !startedRef.current) {
|
||||
timerRef.current = setTimeout((): void => {
|
||||
if (!BProgress.isStarted()) {
|
||||
BProgress.start();
|
||||
startedRef.current = true;
|
||||
}
|
||||
timerRef.current = null;
|
||||
}, PROGRESS_CONFIG.delay);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timerRef.current !== null) {
|
||||
clearTimeout(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, [navigation.state]);
|
||||
|
||||
return null;
|
||||
}
|
||||
1
apps/admin/core/lib/b-progress/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./AppProgressBar";
|
||||
14
apps/admin/middleware.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { next } from '@vercel/edge';
|
||||
|
||||
export default function middleware() {
|
||||
return next({
|
||||
headers: {
|
||||
'Referrer-Policy': 'origin-when-cross-origin',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-DNS-Prefetch-Control': 'on',
|
||||
'Strict-Transport-Security':
|
||||
'max-age=31536000; includeSubDomains; preload',
|
||||
},
|
||||
});
|
||||
}
|
||||
5
apps/admin/next-env.d.ts
vendored
@@ -1,5 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
@@ -1,29 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const nextConfig = {
|
||||
trailingSlash: true,
|
||||
reactStrictMode: false,
|
||||
swcMinify: true,
|
||||
output: "standalone",
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
basePath: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "",
|
||||
experimental: {
|
||||
optimizePackageImports: [
|
||||
"@plane/constants",
|
||||
"@plane/editor",
|
||||
"@plane/hooks",
|
||||
"@plane/i18n",
|
||||
"@plane/logger",
|
||||
"@plane/propel",
|
||||
"@plane/services",
|
||||
"@plane/shared-state",
|
||||
"@plane/types",
|
||||
"@plane/ui",
|
||||
"@plane/utils",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
29
apps/admin/nginx/nginx.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
worker_processes 4;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
|
||||
default_type application/octet-stream;
|
||||
|
||||
set_real_ip_from 0.0.0.0/0;
|
||||
real_ip_recursive on;
|
||||
real_ip_header X-Forward-For;
|
||||
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
|
||||
|
||||
access_log /dev/stdout;
|
||||
error_log /dev/stderr;
|
||||
|
||||
server {
|
||||
listen 3000;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /god-mode/index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,21 @@
|
||||
"version": "1.1.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
"build": "next build",
|
||||
"preview": "next build && next start",
|
||||
"start": "next start",
|
||||
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist",
|
||||
"dev": "cross-env NODE_ENV=development PORT=3001 node server.mjs",
|
||||
"build": "react-router build",
|
||||
"preview": "react-router build && cross-env NODE_ENV=production PORT=3001 node server.mjs",
|
||||
"start": "serve -s build/client -l 3001",
|
||||
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist && rm -rf build",
|
||||
"check:lint": "eslint . --max-warnings 19",
|
||||
"check:types": "tsc --noEmit",
|
||||
"check:types": "react-router typegen && tsc --noEmit",
|
||||
"check:format": "prettier --check \"**/*.{ts,tsx,md,json,css,scss}\"",
|
||||
"fix:lint": "eslint . --fix",
|
||||
"fix:format": "prettier --write \"**/*.{ts,tsx,md,json,css,scss}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@bprogress/core": "catalog:",
|
||||
"@headlessui/react": "^1.7.19",
|
||||
"@plane/constants": "workspace:*",
|
||||
"@plane/hooks": "workspace:*",
|
||||
@@ -25,19 +27,30 @@
|
||||
"@plane/types": "workspace:*",
|
||||
"@plane/ui": "workspace:*",
|
||||
"@plane/utils": "workspace:*",
|
||||
"autoprefixer": "10.4.14",
|
||||
"@react-router/express": "^7.9.3",
|
||||
"@react-router/node": "^7.9.3",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tanstack/virtual-core": "^3.13.12",
|
||||
"@vercel/edge": "1.2.2",
|
||||
"axios": "catalog:",
|
||||
"compression": "^1.8.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^5.1.0",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"isbot": "^5.1.31",
|
||||
"lodash-es": "catalog:",
|
||||
"lucide-react": "catalog:",
|
||||
"mobx": "catalog:",
|
||||
"mobx-react": "catalog:",
|
||||
"next": "catalog:",
|
||||
"morgan": "^1.10.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "^8.4.49",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-hook-form": "7.51.5",
|
||||
"sharp": "catalog:",
|
||||
"react-router": "^7.9.1",
|
||||
"react-router-dom": "^7.9.1",
|
||||
"serve": "14.2.5",
|
||||
"swr": "catalog:",
|
||||
"uuid": "catalog:"
|
||||
},
|
||||
@@ -45,10 +58,16 @@
|
||||
"@plane/eslint-config": "workspace:*",
|
||||
"@plane/tailwind-config": "workspace:*",
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@react-router/dev": "^7.9.1",
|
||||
"@types/compression": "^1.8.1",
|
||||
"@types/express": "4.17.23",
|
||||
"@types/lodash-es": "catalog:",
|
||||
"@types/morgan": "^1.9.10",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
"typescript": "catalog:",
|
||||
"vite": "7.1.7",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,68 +0,0 @@
|
||||
<svg width="1512" height="900" viewBox="0 0 1512 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4817_18724)">
|
||||
<rect width="1512" height="900" fill="#1B1C1E"/>
|
||||
<g opacity="0.09">
|
||||
<line x1="-10.6172" y1="624.328" x2="1500.96" y2="624.328" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="301.59" x2="1500.96" y2="301.59" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="462.958" x2="1500.96" y2="462.958" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="785.696" x2="1500.96" y2="785.696" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="140.22" x2="1500.96" y2="140.22" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="543.642" x2="1500.96" y2="543.642" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="866.381" x2="1500.96" y2="866.381" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="220.904" x2="1500.96" y2="220.904" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="382.272" x2="1500.96" y2="382.272" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="705.012" x2="1500.96" y2="705.013" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="59.534" x2="1500.96" y2="59.534" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="36.3273" y1="-49.8457" x2="36.3273" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="681.808" y1="-49.8457" x2="681.808" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="359.068" y1="-49.8457" x2="359.068" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1004.54" y1="-49.8457" x2="1004.54" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1327.28" y1="-49.8457" x2="1327.28" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="197.698" y1="-49.8457" x2="197.698" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="843.173" y1="-49.8457" x2="843.173" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="520.439" y1="-49.8457" x2="520.439" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1165.92" y1="-49.8457" x2="1165.92" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1488.66" y1="-49.8457" x2="1488.66" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="117.015" y1="-49.8457" x2="117.015" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="762.491" y1="-49.8457" x2="762.491" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="439.751" y1="-49.8457" x2="439.751" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1085.23" y1="-49.8457" x2="1085.23" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1407.97" y1="-49.8457" x2="1407.97" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="278.384" y1="-49.8457" x2="278.384" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="923.861" y1="-49.8457" x2="923.86" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="601.12" y1="-49.8457" x2="601.12" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
<line x1="1246.6" y1="-49.8457" x2="1246.6" y2="1117.56" stroke="white" stroke-opacity="0.18" stroke-width="0.6"/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<rect x="440.141" y="785.309" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1165.39" y="221.433" width="80.8965" height="80" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="520.367" y="463.207" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1085.39" y="301.659" width="80.2262" height="80.3408" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1166" y="382" width="80.2262" height="80.08" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1247" y="301" width="80.2262" height="81.08" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="439.994" y="221.433" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1085.39" y="221.433" width="80" height="80" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="439.994" y="463.207" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="359.914" y="785.309" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="359.914" y="865.535" width="80.2262" height="81.3723" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="198.314" y="705.082" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1005.16" y="59.835" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="924.059" y="-20.8525" width="80.2262" height="79.5062" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="924.059" y="59.4053" width="80.2262" height="80.0285" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="116.943" y="58.6885" width="81.3723" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="36.7168" y="59.835" width="81.3723" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="116.943" y="138.915" width="81.3723" height="82.5184" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="440.141" y="-21.5381" width="81.3723" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="198.316" y="300.513" width="80.2262" height="81.3723" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1166.76" y="705.082" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1246.99" y="785.309" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="37" y="220" width="81" height="82" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="-44" y="140" width="81" height="82" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4817_18724">
|
||||
<rect width="1512" height="900" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -1,68 +0,0 @@
|
||||
<svg width="1512" height="900" viewBox="0 0 1512 900" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4817_18582)">
|
||||
<rect width="1512" height="900" fill="white"/>
|
||||
<g opacity="0.09">
|
||||
<line x1="-10.6172" y1="625.328" x2="1500.96" y2="625.328" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="302.59" x2="1500.96" y2="302.59" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="463.958" x2="1500.96" y2="463.958" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="786.696" x2="1500.96" y2="786.696" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="141.22" x2="1500.96" y2="141.22" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="544.642" x2="1500.96" y2="544.642" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="867.381" x2="1500.96" y2="867.381" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="221.904" x2="1500.96" y2="221.904" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="383.272" x2="1500.96" y2="383.272" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="706.012" x2="1500.96" y2="706.013" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="-10.6172" y1="60.534" x2="1500.96" y2="60.534" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="36.3273" y1="-48.8457" x2="36.3273" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="681.808" y1="-48.8457" x2="681.808" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="359.068" y1="-48.8457" x2="359.068" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1004.54" y1="-48.8457" x2="1004.54" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1327.28" y1="-48.8457" x2="1327.28" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="197.698" y1="-48.8457" x2="197.698" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="843.173" y1="-48.8457" x2="843.173" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="520.439" y1="-48.8457" x2="520.439" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1165.92" y1="-48.8457" x2="1165.92" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1488.66" y1="-48.8457" x2="1488.66" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="117.015" y1="-48.8457" x2="117.015" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="762.491" y1="-48.8457" x2="762.491" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="439.751" y1="-48.8457" x2="439.751" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1085.23" y1="-48.8457" x2="1085.23" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1407.97" y1="-48.8457" x2="1407.97" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="278.384" y1="-48.8457" x2="278.384" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="923.861" y1="-48.8457" x2="923.86" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="601.12" y1="-48.8457" x2="601.12" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
<line x1="1246.6" y1="-48.8457" x2="1246.6" y2="1118.56" stroke="#1F2D5C" stroke-opacity="0.4" stroke-width="0.6"/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<rect x="440.141" y="786.309" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1166.76" y="222.433" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="520.367" y="464.207" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1085.39" y="302.659" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1166" y="383" width="80.2262" height="80.08" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1247" y="302" width="80.2262" height="81.08" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="439.994" y="222.433" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1085.39" y="222.433" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="439.994" y="464.207" width="80.2262" height="79.0801" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="359.914" y="786.309" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="359.914" y="866.535" width="80.2262" height="81.3723" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="198.314" y="706.082" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1005.16" y="60.835" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="924.059" y="-19.8525" width="80.2262" height="79.5062" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="924.059" y="60.4053" width="80.2262" height="80.0285" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="116.943" y="59.6885" width="81.3723" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="36.7168" y="60.835" width="81.3723" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="116.943" y="139.915" width="81.3723" height="82.5184" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="440.141" y="-20.5381" width="81.3723" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="198.316" y="301.513" width="80.2262" height="81.3723" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1166.76" y="706.082" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="1246.99" y="786.309" width="80.2262" height="80.2262" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="37" y="221" width="81" height="82" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
<rect x="-44" y="141" width="81" height="82" fill="#3F76FF" fill-opacity="0.07"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4817_18582">
|
||||
<rect width="1512" height="900" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{ "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 47 KiB |
8
apps/admin/react-router.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Config } from "@react-router/dev/config";
|
||||
|
||||
export default {
|
||||
appDirectory: "app",
|
||||
basename: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH,
|
||||
// Admin runs as a client-side app; build a static client bundle only
|
||||
ssr: false,
|
||||
} satisfies Config;
|
||||
76
apps/admin/server.mjs
Normal file
@@ -0,0 +1,76 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import compression from "compression";
|
||||
import dotenv from "dotenv";
|
||||
import express from "express";
|
||||
import morgan from "morgan";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, ".env") });
|
||||
|
||||
const BUILD_PATH = "./build/server/index.js";
|
||||
const DEVELOPMENT = process.env.NODE_ENV !== "production";
|
||||
|
||||
// Derive the port from NEXT_PUBLIC_ADMIN_BASE_URL when available, otherwise
|
||||
// default to http://localhost:3001 and fall back to PORT env if explicitly set.
|
||||
const DEFAULT_BASE_URL = "http://localhost:3001";
|
||||
const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || DEFAULT_BASE_URL;
|
||||
let parsedBaseUrl;
|
||||
try {
|
||||
parsedBaseUrl = new URL(ADMIN_BASE_URL);
|
||||
} catch {
|
||||
parsedBaseUrl = new URL(DEFAULT_BASE_URL);
|
||||
}
|
||||
|
||||
const PORT = Number.parseInt(parsedBaseUrl.port, 10);
|
||||
|
||||
async function start() {
|
||||
const app = express();
|
||||
|
||||
app.use(compression());
|
||||
app.disable("x-powered-by");
|
||||
|
||||
if (DEVELOPMENT) {
|
||||
console.log("Starting development server");
|
||||
|
||||
const vite = await import("vite").then((vite) =>
|
||||
vite.createServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: "custom",
|
||||
})
|
||||
);
|
||||
|
||||
app.use(vite.middlewares);
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
try {
|
||||
const source = await vite.ssrLoadModule("./server/app.ts");
|
||||
return source.app(req, res, next);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
vite.ssrFixStacktrace(error);
|
||||
}
|
||||
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("Starting production server");
|
||||
|
||||
app.use("/assets", express.static("build/client/assets", { immutable: true, maxAge: "1y" }));
|
||||
app.use(morgan("tiny"));
|
||||
app.use(express.static("build/client", { maxAge: "1h" }));
|
||||
app.use(await import(BUILD_PATH).then((mod) => mod.app));
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
const origin = `${parsedBaseUrl.protocol}//${parsedBaseUrl.hostname}:${PORT}`;
|
||||
console.log(`Server is running on ${origin}`);
|
||||
});
|
||||
}
|
||||
|
||||
start().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
46
apps/admin/server/app.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import "react-router";
|
||||
import { createRequestHandler } from "@react-router/express";
|
||||
import express from "express";
|
||||
import type { Express } from "express";
|
||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
|
||||
const NEXT_PUBLIC_API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL
|
||||
? process.env.NEXT_PUBLIC_API_BASE_URL.replace(/\/$/, "")
|
||||
: "http://127.0.0.1:8000";
|
||||
const NEXT_PUBLIC_API_BASE_PATH = process.env.NEXT_PUBLIC_API_BASE_PATH
|
||||
? process.env.NEXT_PUBLIC_API_BASE_PATH.replace(/\/+$/, "")
|
||||
: "/api";
|
||||
const NORMALIZED_API_BASE_PATH = NEXT_PUBLIC_API_BASE_PATH.startsWith("/")
|
||||
? NEXT_PUBLIC_API_BASE_PATH
|
||||
: `/${NEXT_PUBLIC_API_BASE_PATH}`;
|
||||
const NEXT_PUBLIC_ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH
|
||||
? process.env.NEXT_PUBLIC_ADMIN_BASE_PATH.replace(/\/$/, "")
|
||||
: "/";
|
||||
|
||||
export const app: Express = express();
|
||||
|
||||
// Ensure proxy-aware hostname/URL handling (e.g., X-Forwarded-Host/Proto)
|
||||
// so generated URLs/redirects reflect the public host when behind Nginx.
|
||||
// See related fix in Remix Express adapter.
|
||||
app.set("trust proxy", true);
|
||||
|
||||
app.use(
|
||||
"/api",
|
||||
createProxyMiddleware({
|
||||
target: NEXT_PUBLIC_API_BASE_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
pathRewrite: (path: string) =>
|
||||
NORMALIZED_API_BASE_PATH === "/api" ? path : path.replace(/^\/api/, NORMALIZED_API_BASE_PATH),
|
||||
})
|
||||
);
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(
|
||||
createRequestHandler({
|
||||
build: () => import("virtual:react-router/server-build"),
|
||||
})
|
||||
);
|
||||
|
||||
app.use(NEXT_PUBLIC_ADMIN_BASE_PATH, router);
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "@plane/propel/styles/fonts";
|
||||
@import "@plane/propel/styles/fonts.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@@ -394,3 +394,30 @@ body {
|
||||
:-ms-input-placeholder {
|
||||
color: rgb(var(--color-text-400));
|
||||
}
|
||||
|
||||
/* Progress Bar Styles */
|
||||
:root {
|
||||
--bprogress-color: rgb(var(--color-primary-100)) !important;
|
||||
--bprogress-height: 2.5px !important;
|
||||
}
|
||||
|
||||
.bprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bprogress .bar {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(var(--color-primary-100), 0.8) 0%,
|
||||
rgba(var(--color-primary-100), 1) 100%
|
||||
) !important;
|
||||
will-change: width, opacity;
|
||||
}
|
||||
|
||||
.bprogress .peg {
|
||||
display: block;
|
||||
box-shadow:
|
||||
0 0 8px rgba(var(--color-primary-100), 0.6),
|
||||
0 0 4px rgba(var(--color-primary-100), 0.4) !important;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
{
|
||||
"extends": "@plane/typescript-config/nextjs.json",
|
||||
"extends": "@plane/typescript-config/react-router.json",
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"types": ["node", "vite/client"],
|
||||
"paths": {
|
||||
"@/app/*": ["app/*"],
|
||||
"@/*": ["core/*"],
|
||||
"@/public/*": ["public/*"],
|
||||
"@/plane-admin/*": ["ce/*"],
|
||||
"@/styles/*": ["styles/*"]
|
||||
},
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "next.config.js", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
59
apps/admin/vite.config.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import path from "node:path";
|
||||
import { reactRouter } from "@react-router/dev/vite";
|
||||
import { defineConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { joinUrlPath } from "@plane/utils";
|
||||
|
||||
const PUBLIC_ENV_KEYS = [
|
||||
"NEXT_PUBLIC_API_BASE_URL",
|
||||
"NEXT_PUBLIC_API_BASE_PATH",
|
||||
"NEXT_PUBLIC_ADMIN_BASE_URL",
|
||||
"NEXT_PUBLIC_ADMIN_BASE_PATH",
|
||||
"NEXT_PUBLIC_SPACE_BASE_URL",
|
||||
"NEXT_PUBLIC_SPACE_BASE_PATH",
|
||||
"NEXT_PUBLIC_LIVE_BASE_URL",
|
||||
"NEXT_PUBLIC_LIVE_BASE_PATH",
|
||||
"NEXT_PUBLIC_WEB_BASE_URL",
|
||||
"NEXT_PUBLIC_WEB_BASE_PATH",
|
||||
"NEXT_PUBLIC_WEBSITE_URL",
|
||||
"NEXT_PUBLIC_SUPPORT_EMAIL",
|
||||
];
|
||||
|
||||
const publicEnv = PUBLIC_ENV_KEYS.reduce<Record<string, string>>((acc, key) => {
|
||||
acc[key] = process.env[key] ?? "";
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
export default defineConfig(({ isSsrBuild }) => {
|
||||
// Only produce an SSR bundle when explicitly enabled.
|
||||
// For static deployments (default), we skip the server build entirely.
|
||||
const enableSsrBuild = process.env.ADMIN_ENABLE_SSR_BUILD === "true";
|
||||
const basePath = joinUrlPath(process.env.NEXT_PUBLIC_ADMIN_BASE_PATH ?? "", "/") ?? "/";
|
||||
|
||||
return {
|
||||
base: basePath,
|
||||
define: {
|
||||
"process.env": JSON.stringify(publicEnv),
|
||||
},
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
rollupOptions:
|
||||
isSsrBuild && enableSsrBuild
|
||||
? {
|
||||
input: path.resolve(__dirname, "server/app.ts"),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
plugins: [reactRouter(), tsconfigPaths({ projects: [path.resolve(__dirname, "tsconfig.json")] })],
|
||||
resolve: {
|
||||
alias: {
|
||||
// Next.js compatibility shims used within admin
|
||||
"next/image": path.resolve(__dirname, "app/compat/next/image.tsx"),
|
||||
"next/link": path.resolve(__dirname, "app/compat/next/link.tsx"),
|
||||
"next/navigation": path.resolve(__dirname, "app/compat/next/navigation.ts"),
|
||||
},
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
// No SSR-specific overrides needed; alias resolves to ESM build
|
||||
};
|
||||
});
|
||||
@@ -134,7 +134,8 @@ class InstanceAdminSignUpEndpoint(View):
|
||||
},
|
||||
)
|
||||
url = urljoin(
|
||||
base_host(request=request, is_admin=True),
|
||||
base_host(request=request, is_admin=True, ),
|
||||
|
||||
"?" + urlencode(exc.get_error_dict()),
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
@@ -228,7 +229,7 @@ class InstanceAdminSignUpEndpoint(View):
|
||||
|
||||
# get tokens for user
|
||||
user_login(request=request, user=user, is_admin=True)
|
||||
url = urljoin(base_host(request=request, is_admin=True), "general")
|
||||
url = urljoin(base_host(request=request, is_admin=True), "general/")
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
@@ -347,7 +348,7 @@ class InstanceAdminSignInEndpoint(View):
|
||||
|
||||
# get tokens for user
|
||||
user_login(request=request, user=user, is_admin=True)
|
||||
url = urljoin(base_host(request=request, is_admin=True), "general")
|
||||
url = urljoin(base_host(request=request, is_admin=True), "general/")
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.23",
|
||||
"@types/express": "4.17.23",
|
||||
"@types/express-ws": "^3.0.5",
|
||||
"@types/node": "catalog:",
|
||||
"@types/ws": "^8.18.1",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"devDependencies": {
|
||||
"@plane/eslint-config": "workspace:*",
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express": "4.17.23",
|
||||
"@types/node": "catalog:",
|
||||
"@types/ws": "^8.5.10",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"require": "./dist/lib.cjs"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./styles.css": "./dist/styles/index.css",
|
||||
"./styles": "./dist/styles/index.css"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -6,8 +6,9 @@ export default defineConfig({
|
||||
format: ["esm", "cjs"],
|
||||
copy: ["src/styles"],
|
||||
exports: {
|
||||
customExports: (out) => ({
|
||||
...out,
|
||||
customExports: (exports) => ({
|
||||
...exports,
|
||||
"./styles.css": "./dist/styles/index.css",
|
||||
"./styles": "./dist/styles/index.css",
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
"server.js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.6.0",
|
||||
"@typescript-eslint/parser": "^8.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||
"@typescript-eslint/parser": "^8.45.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-next": "^14.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "^1.12.4",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-config-turbo": "^2.5.8",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^6.1.1",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,16 @@
|
||||
"dependencies": {
|
||||
"@plane/utils": "workspace:*",
|
||||
"intl-messageformat": "^10.7.11",
|
||||
"lodash-es": "catalog:",
|
||||
"mobx": "catalog:",
|
||||
"mobx-react": "catalog:",
|
||||
"lodash-es": "catalog:",
|
||||
"react": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@plane/eslint-config": "workspace:*",
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"@types/lodash-es": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"devDependencies": {
|
||||
"@plane/eslint-config": "workspace:*",
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express": "4.17.23",
|
||||
"@types/node": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -165,7 +165,9 @@
|
||||
"require": "./dist/utils/index.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./styles/fonts.css": "./dist/styles/fonts/index.css",
|
||||
"./styles/fonts": "./dist/styles/fonts/index.css",
|
||||
"./styles/react-day-picker.css": "./dist/styles/react-day-picker.css",
|
||||
"./styles/react-day-picker": "./dist/styles/react-day-picker.css"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -38,9 +38,11 @@ export default defineConfig({
|
||||
outDir: "dist",
|
||||
format: ["esm", "cjs"],
|
||||
exports: {
|
||||
customExports: (out) => ({
|
||||
...out,
|
||||
customExports: (exports) => ({
|
||||
...exports,
|
||||
"./styles/fonts.css": "./dist/styles/fonts/index.css",
|
||||
"./styles/fonts": "./dist/styles/fonts/index.css",
|
||||
"./styles/react-day-picker.css": "./dist/styles/react-day-picker.css",
|
||||
"./styles/react-day-picker": "./dist/styles/react-day-picker.css",
|
||||
}),
|
||||
},
|
||||
|
||||