mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: merge conflicts resolved from preview
This commit is contained in:
commit
70f21649ed
@ -4,7 +4,7 @@ module.exports = {
|
|||||||
extends: ["custom"],
|
extends: ["custom"],
|
||||||
settings: {
|
settings: {
|
||||||
next: {
|
next: {
|
||||||
rootDir: ["web/", "space/"],
|
rootDir: ["web/", "space/", "admin/"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
32
.github/workflows/build-test-pull-request.yml
vendored
32
.github/workflows/build-test-pull-request.yml
vendored
@ -14,10 +14,10 @@ jobs:
|
|||||||
space_changed: ${{ steps.changed-files.outputs.space_any_changed }}
|
space_changed: ${{ steps.changed-files.outputs.space_any_changed }}
|
||||||
web_changed: ${{ steps.changed-files.outputs.web_any_changed }}
|
web_changed: ${{ steps.changed-files.outputs.web_any_changed }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@v41
|
uses: tj-actions/changed-files@v44
|
||||||
with:
|
with:
|
||||||
files_yaml: |
|
files_yaml: |
|
||||||
apiserver:
|
apiserver:
|
||||||
@ -49,9 +49,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: needs.get-changed-files.outputs.apiserver_changed == 'true'
|
if: needs.get-changed-files.outputs.apiserver_changed == 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x" # Specify the Python version you need
|
python-version: "3.x" # Specify the Python version you need
|
||||||
- name: Install Pylint
|
- name: Install Pylint
|
||||||
@ -66,9 +66,9 @@ jobs:
|
|||||||
if: needs.get-changed-files.outputs.admin_changed == 'true'
|
if: needs.get-changed-files.outputs.admin_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@ -79,9 +79,9 @@ jobs:
|
|||||||
if: needs.get-changed-files.outputs.space_changed == 'true'
|
if: needs.get-changed-files.outputs.space_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@ -92,9 +92,9 @@ jobs:
|
|||||||
if: needs.get-changed-files.outputs.web_changed == 'true'
|
if: needs.get-changed-files.outputs.web_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@ -104,9 +104,9 @@ jobs:
|
|||||||
needs: lint-admin
|
needs: lint-admin
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@ -116,9 +116,9 @@ jobs:
|
|||||||
needs: lint-space
|
needs: lint-space
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@ -128,9 +128,9 @@ jobs:
|
|||||||
needs: lint-web
|
needs: lint-web
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
|
28
.github/workflows/feature-deployment.yml
vendored
28
.github/workflows/feature-deployment.yml
vendored
@ -5,17 +5,17 @@ on:
|
|||||||
inputs:
|
inputs:
|
||||||
web-build:
|
web-build:
|
||||||
required: false
|
required: false
|
||||||
description: 'Build Web'
|
description: "Build Web"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
space-build:
|
space-build:
|
||||||
required: false
|
required: false
|
||||||
description: 'Build Space'
|
description: "Build Space"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
admin-build:
|
admin-build:
|
||||||
required: false
|
required: false
|
||||||
description: 'Build Admin'
|
description: "Build Admin"
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ jobs:
|
|||||||
echo "BUILD_SPACE=$BUILD_SPACE"
|
echo "BUILD_SPACE=$BUILD_SPACE"
|
||||||
echo "BUILD_ADMIN=$BUILD_ADMIN"
|
echo "BUILD_ADMIN=$BUILD_ADMIN"
|
||||||
outputs:
|
outputs:
|
||||||
web-build: ${{ env.BUILD_WEB}}
|
web-build: ${{ env.BUILD_WEB}}
|
||||||
space-build: ${{env.BUILD_SPACE}}
|
space-build: ${{env.BUILD_SPACE}}
|
||||||
admin-build: ${{env.BUILD_ADMIN}}
|
admin-build: ${{env.BUILD_ADMIN}}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: "18"
|
||||||
- name: Install AWS cli
|
- name: Install AWS cli
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@ -79,7 +79,7 @@ jobs:
|
|||||||
|
|
||||||
FILE_EXPIRY=$(date -u -d "+2 days" +"%Y-%m-%dT%H:%M:%SZ")
|
FILE_EXPIRY=$(date -u -d "+2 days" +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
aws s3 cp $TAR_NAME s3://${{ env.AWS_BUCKET }}/${{github.sha}}/$TAR_NAME --expires $FILE_EXPIRY
|
aws s3 cp $TAR_NAME s3://${{ env.AWS_BUCKET }}/${{github.sha}}/$TAR_NAME --expires $FILE_EXPIRY
|
||||||
|
|
||||||
feature-build-space:
|
feature-build-space:
|
||||||
if: ${{ needs.setup-feature-build.outputs.space-build == 'true' }}
|
if: ${{ needs.setup-feature-build.outputs.space-build == 'true' }}
|
||||||
needs: setup-feature-build
|
needs: setup-feature-build
|
||||||
@ -89,7 +89,7 @@ jobs:
|
|||||||
AWS_ACCESS_KEY_ID: ${{ vars.FEATURE_PREVIEW_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ vars.FEATURE_PREVIEW_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.FEATURE_PREVIEW_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.FEATURE_PREVIEW_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_BUCKET: ${{ vars.FEATURE_PREVIEW_AWS_BUCKET }}
|
AWS_BUCKET: ${{ vars.FEATURE_PREVIEW_AWS_BUCKET }}
|
||||||
NEXT_PUBLIC_DEPLOY_WITH_NGINX: 1
|
NEXT_PUBLIC_SPACE_BASE_PATH: "/spaces"
|
||||||
NEXT_PUBLIC_API_BASE_URL: ${{ vars.FEATURE_PREVIEW_NEXT_PUBLIC_API_BASE_URL }}
|
NEXT_PUBLIC_API_BASE_URL: ${{ vars.FEATURE_PREVIEW_NEXT_PUBLIC_API_BASE_URL }}
|
||||||
outputs:
|
outputs:
|
||||||
do-build: ${{ needs.setup-feature-build.outputs.space-build }}
|
do-build: ${{ needs.setup-feature-build.outputs.space-build }}
|
||||||
@ -98,7 +98,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: "18"
|
||||||
- name: Install AWS cli
|
- name: Install AWS cli
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@ -134,7 +134,7 @@ jobs:
|
|||||||
AWS_ACCESS_KEY_ID: ${{ vars.FEATURE_PREVIEW_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ vars.FEATURE_PREVIEW_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.FEATURE_PREVIEW_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.FEATURE_PREVIEW_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_BUCKET: ${{ vars.FEATURE_PREVIEW_AWS_BUCKET }}
|
AWS_BUCKET: ${{ vars.FEATURE_PREVIEW_AWS_BUCKET }}
|
||||||
NEXT_PUBLIC_DEPLOY_WITH_NGINX: 1
|
NEXT_PUBLIC_ADMIN_BASE_PATH: "/god-mode"
|
||||||
NEXT_PUBLIC_API_BASE_URL: ${{ vars.FEATURE_PREVIEW_NEXT_PUBLIC_API_BASE_URL }}
|
NEXT_PUBLIC_API_BASE_URL: ${{ vars.FEATURE_PREVIEW_NEXT_PUBLIC_API_BASE_URL }}
|
||||||
outputs:
|
outputs:
|
||||||
do-build: ${{ needs.setup-feature-build.outputs.admin-build }}
|
do-build: ${{ needs.setup-feature-build.outputs.admin-build }}
|
||||||
@ -143,7 +143,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: "18"
|
||||||
- name: Install AWS cli
|
- name: Install AWS cli
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
@ -172,7 +172,13 @@ jobs:
|
|||||||
|
|
||||||
feature-deploy:
|
feature-deploy:
|
||||||
if: ${{ always() && (needs.setup-feature-build.outputs.web-build == 'true' || needs.setup-feature-build.outputs.space-build == 'true' || needs.setup-feature-build.outputs.admin-build == 'true') }}
|
if: ${{ always() && (needs.setup-feature-build.outputs.web-build == 'true' || needs.setup-feature-build.outputs.space-build == 'true' || needs.setup-feature-build.outputs.admin-build == 'true') }}
|
||||||
needs: [setup-feature-build, feature-build-web, feature-build-space, feature-build-admin]
|
needs:
|
||||||
|
[
|
||||||
|
setup-feature-build,
|
||||||
|
feature-build-web,
|
||||||
|
feature-build-space,
|
||||||
|
feature-build-admin,
|
||||||
|
]
|
||||||
name: Feature Deploy
|
name: Feature Deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
NEXT_PUBLIC_APP_URL=
|
NEXT_PUBLIC_API_BASE_URL=""
|
||||||
NEXT_PUBLIC_API_BASE_URL=
|
NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||||
|
NEXT_PUBLIC_WEB_BASE_URL=""
|
@ -10,5 +10,43 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {}
|
rules: {
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
groups: ["builtin", "external", "internal", "parent", "sibling",],
|
||||||
|
pathGroups: [
|
||||||
|
{
|
||||||
|
pattern: "react",
|
||||||
|
group: "external",
|
||||||
|
position: "before",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "lucide-react",
|
||||||
|
group: "external",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "@headlessui/**",
|
||||||
|
group: "external",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "@plane/**",
|
||||||
|
group: "external",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "@/**",
|
||||||
|
group: "internal",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pathGroupsExcludedImportTypes: ["builtin", "internal", "react"],
|
||||||
|
alphabetize: {
|
||||||
|
order: "asc",
|
||||||
|
caseInsensitive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
# STAGE 1: Build the project
|
||||||
|
# *****************************************************************************
|
||||||
FROM node:18-alpine AS builder
|
FROM node:18-alpine AS builder
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -7,6 +10,9 @@ COPY . .
|
|||||||
|
|
||||||
RUN turbo prune --scope=admin --docker
|
RUN turbo prune --scope=admin --docker
|
||||||
|
|
||||||
|
# *****************************************************************************
|
||||||
|
# STAGE 2: Install dependencies & build the project
|
||||||
|
# *****************************************************************************
|
||||||
FROM node:18-alpine AS installer
|
FROM node:18-alpine AS installer
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
@ -21,13 +27,25 @@ COPY --from=builder /app/out/full/ .
|
|||||||
COPY turbo.json turbo.json
|
COPY turbo.json turbo.json
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||||
ARG NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
|
|
||||||
|
|
||||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||||
ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX
|
|
||||||
|
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
||||||
|
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
|
||||||
|
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||||
|
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
|
||||||
|
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
ENV TURBO_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
RUN yarn turbo run build --filter=admin
|
RUN yarn turbo run build --filter=admin
|
||||||
|
|
||||||
|
# *****************************************************************************
|
||||||
|
# STAGE 3: Copy the project and start it
|
||||||
|
# *****************************************************************************
|
||||||
FROM node:18-alpine AS runner
|
FROM node:18-alpine AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@ -38,12 +56,17 @@ COPY --from=installer /app/admin/.next/standalone ./
|
|||||||
COPY --from=installer /app/admin/.next/static ./admin/.next/static
|
COPY --from=installer /app/admin/.next/static ./admin/.next/static
|
||||||
COPY --from=installer /app/admin/public ./admin/public
|
COPY --from=installer /app/admin/public ./admin/public
|
||||||
|
|
||||||
|
|
||||||
ARG NEXT_PUBLIC_API_BASE_URL=""
|
ARG NEXT_PUBLIC_API_BASE_URL=""
|
||||||
ARG NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
|
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||||
|
|
||||||
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
ARG NEXT_PUBLIC_WEB_BASE_URL=""
|
||||||
ENV NEXT_PUBLIC_DEPLOY_WITH_NGINX=$NEXT_PUBLIC_DEPLOY_WITH_NGINX
|
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
|
||||||
|
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||||
|
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
ENV TURBO_TELEMETRY_DISABLED 1
|
ENV TURBO_TELEMETRY_DISABLED 1
|
||||||
|
@ -3,10 +3,15 @@ RUN apk add --no-cache libc6-compat
|
|||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN yarn global add turbo
|
RUN yarn global add turbo
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
|
ENV NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
VOLUME [ "/app/node_modules", "/app/admin/node_modules" ]
|
VOLUME [ "/app/node_modules", "/app/admin/node_modules" ]
|
||||||
|
|
||||||
CMD ["yarn", "dev", "--filter=admin"]
|
CMD ["yarn", "dev", "--filter=admin"]
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from "./ai-config-form";
|
|
@ -1,12 +1,12 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Lightbulb } from "lucide-react";
|
import { Lightbulb } from "lucide-react";
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
|
||||||
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
||||||
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput, TControllerInputFormField } from "components/common";
|
import { ControllerInput, TControllerInputFormField } from "@/components/common";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type IInstanceAIForm = {
|
type IInstanceAIForm = {
|
||||||
config: IFormattedInstanceConfiguration;
|
config: IFormattedInstanceConfiguration;
|
@ -2,20 +2,8 @@
|
|||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
// layouts
|
||||||
import { AdminLayout } from "@/layouts";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
// lib
|
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
|
||||||
|
|
||||||
interface AILayoutProps {
|
export default function AILayout({ children }: { children: ReactNode }) {
|
||||||
children: ReactNode;
|
return <AdminLayout>{children}</AdminLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AILayout = ({ children }: AILayoutProps) => (
|
|
||||||
<InstanceWrapper>
|
|
||||||
<AuthWrapper>
|
|
||||||
<AdminLayout>{children}</AdminLayout>
|
|
||||||
</AuthWrapper>
|
|
||||||
</InstanceWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default AILayout;
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import useSWR from "swr";
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
import { InstanceAIForm } from "./components";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// components
|
||||||
|
import { InstanceAIForm } from "./form";
|
||||||
|
|
||||||
const InstanceAIPage = observer(() => {
|
const InstanceAIPage = observer(() => {
|
||||||
// store
|
// store
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from "./authentication-method-card";
|
|
@ -3,11 +3,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// ui
|
|
||||||
import { ToggleSwitch } from "@plane/ui";
|
|
||||||
// types
|
|
||||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||||
|
import { ToggleSwitch } from "@plane/ui";
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// ui
|
||||||
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Link from "next/link";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
import Link from "next/link";
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// ui
|
|
||||||
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
|
|
||||||
// icons
|
// icons
|
||||||
import { Settings2 } from "lucide-react";
|
import { Settings2 } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||||
|
// ui
|
||||||
|
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
@ -1,18 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Link from "next/link";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
import Link from "next/link";
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// ui
|
|
||||||
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
|
|
||||||
// icons
|
// icons
|
||||||
import { Settings2 } from "lucide-react";
|
import { Settings2 } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||||
|
// ui
|
||||||
|
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
@ -1,3 +1,5 @@
|
|||||||
export * from "./common";
|
|
||||||
export * from "./email-config-switch";
|
export * from "./email-config-switch";
|
||||||
export * from "./password-config-switch";
|
export * from "./password-config-switch";
|
||||||
|
export * from "./authentication-method-card";
|
||||||
|
export * from "./github-config";
|
||||||
|
export * from "./google-config";
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// ui
|
|
||||||
import { ToggleSwitch } from "@plane/ui";
|
|
||||||
// types
|
|
||||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||||
|
import { ToggleSwitch } from "@plane/ui";
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// ui
|
||||||
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./root";
|
|
||||||
export * from "./github-config-form";
|
|
@ -1,8 +1,9 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import isEmpty from "lodash/isEmpty";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
// hooks
|
import { useForm } from "react-hook-form";
|
||||||
import { useInstance } from "@/hooks";
|
// types
|
||||||
|
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@ -12,12 +13,11 @@ import {
|
|||||||
CopyField,
|
CopyField,
|
||||||
TControllerInputFormField,
|
TControllerInputFormField,
|
||||||
TCopyField,
|
TCopyField,
|
||||||
} from "components/common";
|
} from "@/components/common";
|
||||||
// types
|
|
||||||
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL, cn } from "helpers/common.helper";
|
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||||
import isEmpty from "lodash/isEmpty";
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: IFormattedInstanceConfiguration;
|
config: IFormattedInstanceConfiguration;
|
||||||
@ -46,7 +46,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
|
|
||||||
const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : "";
|
const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : "";
|
||||||
|
|
||||||
const githubFormFields: TControllerInputFormField[] = [
|
const GITHUB_FORM_FIELDS: TControllerInputFormField[] = [
|
||||||
{
|
{
|
||||||
key: "GITHUB_CLIENT_ID",
|
key: "GITHUB_CLIENT_ID",
|
||||||
type: "text",
|
type: "text",
|
||||||
@ -55,6 +55,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
You will get this from your{" "}
|
You will get this from your{" "}
|
||||||
<a
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
href="https://github.com/settings/applications/new"
|
href="https://github.com/settings/applications/new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-custom-primary-100 hover:underline"
|
className="text-custom-primary-100 hover:underline"
|
||||||
@ -76,6 +77,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
Your client secret is also found in your{" "}
|
Your client secret is also found in your{" "}
|
||||||
<a
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
href="https://github.com/settings/applications/new"
|
href="https://github.com/settings/applications/new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-custom-primary-100 hover:underline"
|
className="text-custom-primary-100 hover:underline"
|
||||||
@ -91,7 +93,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const githubCopyFields: TCopyField[] = [
|
const GITHUB_SERVICE_FIELD: TCopyField[] = [
|
||||||
{
|
{
|
||||||
key: "Origin_URL",
|
key: "Origin_URL",
|
||||||
label: "Origin URL",
|
label: "Origin URL",
|
||||||
@ -100,6 +102,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
We will auto-generate this. Paste this into the Authorized origin URL field{" "}
|
We will auto-generate this. Paste this into the Authorized origin URL field{" "}
|
||||||
<a
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
href="https://github.com/settings/applications/new"
|
href="https://github.com/settings/applications/new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-custom-primary-100 hover:underline"
|
className="text-custom-primary-100 hover:underline"
|
||||||
@ -118,6 +121,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
We will auto-generate this. Paste this into your Authorized Callback URI field{" "}
|
We will auto-generate this. Paste this into your Authorized Callback URI field{" "}
|
||||||
<a
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
href="https://github.com/settings/applications/new"
|
href="https://github.com/settings/applications/new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-custom-primary-100 hover:underline"
|
className="text-custom-primary-100 hover:underline"
|
||||||
@ -134,13 +138,16 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
const payload: Partial<GithubConfigFormValues> = { ...formData };
|
const payload: Partial<GithubConfigFormValues> = { ...formData };
|
||||||
|
|
||||||
await updateInstanceConfigurations(payload)
|
await updateInstanceConfigurations(payload)
|
||||||
.then(() => {
|
.then((response = []) => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Success",
|
title: "Success",
|
||||||
message: "Github Configuration Settings updated successfully",
|
message: "Github Configuration Settings updated successfully",
|
||||||
});
|
});
|
||||||
reset();
|
reset({
|
||||||
|
GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value,
|
||||||
|
GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => console.error(err));
|
||||||
};
|
};
|
||||||
@ -163,7 +170,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
||||||
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1">
|
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1">
|
||||||
<div className="pt-2 text-xl font-medium">Configuration</div>
|
<div className="pt-2 text-xl font-medium">Configuration</div>
|
||||||
{githubFormFields.map((field) => (
|
{GITHUB_FORM_FIELDS.map((field) => (
|
||||||
<ControllerInput
|
<ControllerInput
|
||||||
key={field.key}
|
key={field.key}
|
||||||
control={control}
|
control={control}
|
||||||
@ -194,7 +201,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
|||||||
<div className="col-span-2 md:col-span-1">
|
<div className="col-span-2 md:col-span-1">
|
||||||
<div className="flex flex-col gap-y-4 px-6 py-4 my-2 bg-custom-background-80/60 rounded-lg">
|
<div className="flex flex-col gap-y-4 px-6 py-4 my-2 bg-custom-background-80/60 rounded-lg">
|
||||||
<div className="pt-2 text-xl font-medium">Service provider details</div>
|
<div className="pt-2 text-xl font-medium">Service provider details</div>
|
||||||
{githubCopyFields.map((field) => (
|
{GITHUB_SERVICE_FIELD.map((field) => (
|
||||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
@ -1,22 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
import { AuthenticationMethodCard } from "../components";
|
|
||||||
import { InstanceGithubConfigForm } from "./components";
|
|
||||||
// hooks
|
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
// icons
|
// icons
|
||||||
import githubLightModeImage from "@/public/logos/github-black.png";
|
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||||
import githubDarkModeImage from "@/public/logos/github-white.png";
|
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||||
|
// local components
|
||||||
|
import { AuthenticationMethodCard } from "../components";
|
||||||
|
import { InstanceGithubConfigForm } from "./form";
|
||||||
|
|
||||||
const InstanceGithubAuthenticationPage = observer(() => {
|
const InstanceGithubAuthenticationPage = observer(() => {
|
||||||
// store
|
// store
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./root";
|
|
||||||
export * from "./google-config-form";
|
|
@ -1,8 +1,9 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import isEmpty from "lodash/isEmpty";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
// hooks
|
import { useForm } from "react-hook-form";
|
||||||
import { useInstance } from "@/hooks";
|
// types
|
||||||
|
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
@ -12,12 +13,11 @@ import {
|
|||||||
CopyField,
|
CopyField,
|
||||||
TControllerInputFormField,
|
TControllerInputFormField,
|
||||||
TCopyField,
|
TCopyField,
|
||||||
} from "components/common";
|
} from "@/components/common";
|
||||||
// types
|
|
||||||
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL, cn } from "helpers/common.helper";
|
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||||
import isEmpty from "lodash/isEmpty";
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: IFormattedInstanceConfiguration;
|
config: IFormattedInstanceConfiguration;
|
||||||
@ -46,7 +46,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
|
|
||||||
const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : "";
|
const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : "";
|
||||||
|
|
||||||
const googleFormFields: TControllerInputFormField[] = [
|
const GOOGLE_FORM_FIELDS: TControllerInputFormField[] = [
|
||||||
{
|
{
|
||||||
key: "GOOGLE_CLIENT_ID",
|
key: "GOOGLE_CLIENT_ID",
|
||||||
type: "text",
|
type: "text",
|
||||||
@ -55,6 +55,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
Your client ID lives in your Google API Console.{" "}
|
Your client ID lives in your Google API Console.{" "}
|
||||||
<a
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
href="https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#creatingcred"
|
href="https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#creatingcred"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-custom-primary-100 hover:underline"
|
className="text-custom-primary-100 hover:underline"
|
||||||
@ -76,6 +77,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
<>
|
<>
|
||||||
Your client secret should also be in your Google API Console.{" "}
|
Your client secret should also be in your Google API Console.{" "}
|
||||||
<a
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
href="https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid"
|
href="https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-custom-primary-100 hover:underline"
|
className="text-custom-primary-100 hover:underline"
|
||||||
@ -91,7 +93,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const googleCopyFeilds: TCopyField[] = [
|
const GOOGLE_SERVICE_DETAILS: TCopyField[] = [
|
||||||
{
|
{
|
||||||
key: "Origin_URL",
|
key: "Origin_URL",
|
||||||
label: "Origin URL",
|
label: "Origin URL",
|
||||||
@ -134,13 +136,16 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
const payload: Partial<GoogleConfigFormValues> = { ...formData };
|
const payload: Partial<GoogleConfigFormValues> = { ...formData };
|
||||||
|
|
||||||
await updateInstanceConfigurations(payload)
|
await updateInstanceConfigurations(payload)
|
||||||
.then(() => {
|
.then((response = []) => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Success",
|
title: "Success",
|
||||||
message: "Google Configuration Settings updated successfully",
|
message: "Google Configuration Settings updated successfully",
|
||||||
});
|
});
|
||||||
reset();
|
reset({
|
||||||
|
GOOGLE_CLIENT_ID: response.find((item) => item.key === "GOOGLE_CLIENT_ID")?.value,
|
||||||
|
GOOGLE_CLIENT_SECRET: response.find((item) => item.key === "GOOGLE_CLIENT_SECRET")?.value,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => console.error(err));
|
||||||
};
|
};
|
||||||
@ -163,7 +168,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
|
||||||
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1">
|
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1">
|
||||||
<div className="pt-2 text-xl font-medium">Configuration</div>
|
<div className="pt-2 text-xl font-medium">Configuration</div>
|
||||||
{googleFormFields.map((field) => (
|
{GOOGLE_FORM_FIELDS.map((field) => (
|
||||||
<ControllerInput
|
<ControllerInput
|
||||||
key={field.key}
|
key={field.key}
|
||||||
control={control}
|
control={control}
|
||||||
@ -194,7 +199,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
|||||||
<div className="col-span-2 md:col-span-1">
|
<div className="col-span-2 md:col-span-1">
|
||||||
<div className="flex flex-col gap-y-4 px-6 py-4 my-2 bg-custom-background-80/60 rounded-lg">
|
<div className="flex flex-col gap-y-4 px-6 py-4 my-2 bg-custom-background-80/60 rounded-lg">
|
||||||
<div className="pt-2 text-xl font-medium">Service provider details</div>
|
<div className="pt-2 text-xl font-medium">Service provider details</div>
|
||||||
{googleCopyFeilds.map((field) => (
|
{GOOGLE_SERVICE_DETAILS.map((field) => (
|
||||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
@ -1,18 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Image from "next/image";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import Image from "next/image";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
import { AuthenticationMethodCard } from "../components";
|
|
||||||
import { InstanceGoogleConfigForm } from "./components";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
// icons
|
// icons
|
||||||
import GoogleLogo from "@/public/logos/google-logo.svg";
|
import GoogleLogo from "@/public/logos/google-logo.svg";
|
||||||
|
// local components
|
||||||
|
import { AuthenticationMethodCard } from "../components";
|
||||||
|
import { InstanceGoogleConfigForm } from "./form";
|
||||||
|
|
||||||
const InstanceGoogleAuthenticationPage = observer(() => {
|
const InstanceGoogleAuthenticationPage = observer(() => {
|
||||||
// store
|
// store
|
||||||
|
@ -2,20 +2,8 @@
|
|||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
// layouts
|
||||||
import { AdminLayout } from "@/layouts";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
// lib
|
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
|
||||||
|
|
||||||
interface AuthenticationLayoutProps {
|
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
||||||
children: ReactNode;
|
return <AdminLayout>{children}</AdminLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthenticationLayout = ({ children }: AuthenticationLayoutProps) => (
|
|
||||||
<InstanceWrapper>
|
|
||||||
<AuthWrapper>
|
|
||||||
<AdminLayout>{children}</AdminLayout>
|
|
||||||
</AuthWrapper>
|
|
||||||
</InstanceWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default AuthenticationLayout;
|
|
||||||
|
@ -1,26 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { Mails, KeyRound } from "lucide-react";
|
import { Mails, KeyRound } from "lucide-react";
|
||||||
import { Loader, setPromiseToast } from "@plane/ui";
|
|
||||||
import { TInstanceConfigurationKeys } from "@plane/types";
|
import { TInstanceConfigurationKeys } from "@plane/types";
|
||||||
|
import { Loader, setPromiseToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { AuthenticationMethodCard, EmailCodesConfiguration, PasswordLoginConfiguration } from "./components";
|
|
||||||
import { GoogleConfiguration } from "./google/components";
|
|
||||||
import { GithubConfiguration } from "./github/components";
|
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
// images
|
// images
|
||||||
import GoogleLogo from "@/public/logos/google-logo.svg";
|
|
||||||
import githubLightModeImage from "@/public/logos/github-black.png";
|
import githubLightModeImage from "@/public/logos/github-black.png";
|
||||||
import githubDarkModeImage from "@/public/logos/github-white.png";
|
import githubDarkModeImage from "@/public/logos/github-white.png";
|
||||||
|
import GoogleLogo from "@/public/logos/google-logo.svg";
|
||||||
|
// local components
|
||||||
|
import {
|
||||||
|
AuthenticationMethodCard,
|
||||||
|
EmailCodesConfiguration,
|
||||||
|
PasswordLoginConfiguration,
|
||||||
|
GithubConfiguration,
|
||||||
|
GoogleConfiguration,
|
||||||
|
} from "./components";
|
||||||
|
|
||||||
type TInstanceAuthenticationMethodCard = {
|
type TInstanceAuthenticationMethodCard = {
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React, { FC, useMemo, useState } from "react";
|
import React, { FC, useMemo, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
|
||||||
// ui
|
|
||||||
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// ui
|
||||||
// components
|
// components
|
||||||
import { ControllerInput, TControllerInputFormField } from "components/common";
|
import { ControllerInput, TControllerInputFormField } from "components/common";
|
||||||
import { SendTestEmailModal } from "./test-email-modal";
|
import { SendTestEmailModal } from "./test-email-modal";
|
||||||
// types
|
// types
|
||||||
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
|
|
||||||
|
|
||||||
type IInstanceEmailForm = {
|
type IInstanceEmailForm = {
|
||||||
config: IFormattedInstanceConfiguration;
|
config: IFormattedInstanceConfiguration;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
// layouts
|
||||||
import { AdminLayout } from "@/layouts";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
// lib
|
// lib
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import useSWR from "swr";
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
import { InstanceEmailForm } from "./components";
|
import { InstanceEmailForm } from "./components";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
|
|
||||||
const InstanceEmailPage = observer(() => {
|
const InstanceEmailPage = observer(() => {
|
||||||
// store
|
// store
|
||||||
|
9
admin/app/error.tsx
Normal file
9
admin/app/error.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function RootErrorPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>Something went wrong.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
export * from "./general-config-form";
|
|
@ -1,19 +1,23 @@
|
|||||||
|
"use client";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { Telescope } from "lucide-react";
|
import { Telescope } from "lucide-react";
|
||||||
|
// types
|
||||||
import { IInstance, IInstanceAdmin } from "@plane/types";
|
import { IInstance, IInstanceAdmin } from "@plane/types";
|
||||||
|
// ui
|
||||||
import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
|
import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput } from "components/common";
|
import { ControllerInput } from "@/components/common";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
export interface IGeneralConfigurationForm {
|
export interface IGeneralConfigurationForm {
|
||||||
instance: IInstance["instance"];
|
instance: IInstance["instance"];
|
||||||
instanceAdmins: IInstanceAdmin[];
|
instanceAdmins: IInstanceAdmin[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) => {
|
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer((props) => {
|
||||||
const { instance, instanceAdmins } = props;
|
const { instance, instanceAdmins } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { updateInstanceInfo } = useInstance();
|
const { updateInstanceInfo } = useInstance();
|
||||||
@ -24,8 +28,8 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) =
|
|||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<Partial<IInstance["instance"]>>({
|
} = useForm<Partial<IInstance["instance"]>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
instance_name: instance.instance_name,
|
instance_name: instance?.instance_name,
|
||||||
is_telemetry_enabled: instance.is_telemetry_enabled,
|
is_telemetry_enabled: instance?.is_telemetry_enabled,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,4 +137,4 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
@ -1,21 +1,12 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
import { Metadata } from "next";
|
||||||
import { AdminLayout } from "@/layouts";
|
// components
|
||||||
// lib
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
|
||||||
|
|
||||||
interface GeneralLayoutProps {
|
export const metadata: Metadata = {
|
||||||
children: ReactNode;
|
title: "General Settings - God Mode",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GeneralLayout({ children }: { children: ReactNode }) {
|
||||||
|
return <AdminLayout>{children}</AdminLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GeneralLayout = ({ children }: GeneralLayoutProps) => (
|
|
||||||
<InstanceWrapper>
|
|
||||||
<AuthWrapper>
|
|
||||||
<AdminLayout>{children}</AdminLayout>
|
|
||||||
</AuthWrapper>
|
|
||||||
</InstanceWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default GeneralLayout;
|
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
|
||||||
import { PageHeader } from "@/components/core";
|
|
||||||
import { GeneralConfigurationForm } from "./components";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// components
|
||||||
|
import { GeneralConfigurationForm } from "./form";
|
||||||
|
|
||||||
const GeneralPage = observer(() => {
|
function GeneralPage() {
|
||||||
const { instance, instanceAdmins } = useInstance();
|
const { instance, instanceAdmins } = useInstance();
|
||||||
|
console.log("instance", instanceAdmins);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader title="General Settings - God Mode" />
|
|
||||||
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
|
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
|
||||||
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
|
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
|
||||||
<div className="text-xl font-medium text-custom-text-100">General settings</div>
|
<div className="text-xl font-medium text-custom-text-100">General settings</div>
|
||||||
@ -22,13 +19,13 @@ const GeneralPage = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow overflow-hidden overflow-y-auto">
|
<div className="flex-grow overflow-hidden overflow-y-auto">
|
||||||
{instance?.instance && instanceAdmins && instanceAdmins?.length > 0 && (
|
{instance?.instance && instanceAdmins && (
|
||||||
<GeneralConfigurationForm instance={instance?.instance} instanceAdmins={instanceAdmins} />
|
<GeneralConfigurationForm instance={instance.instance} instanceAdmins={instanceAdmins} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
export default GeneralPage;
|
export default observer(GeneralPage);
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from "./image-config-form";
|
|
@ -1,11 +1,11 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
|
||||||
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
||||||
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ControllerInput } from "components/common";
|
import { ControllerInput } from "@/components/common";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
|
||||||
type IInstanceImageConfigForm = {
|
type IInstanceImageConfigForm = {
|
||||||
config: IFormattedInstanceConfiguration;
|
config: IFormattedInstanceConfiguration;
|
@ -1,21 +1,11 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// layouts
|
// layouts
|
||||||
import { AdminLayout } from "@/layouts";
|
import { AdminLayout } from "@/layouts/admin-layout";
|
||||||
// lib
|
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
|
||||||
|
|
||||||
interface ImageLayoutProps {
|
interface ImageLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageLayout = ({ children }: ImageLayoutProps) => (
|
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
||||||
<InstanceWrapper>
|
|
||||||
<AuthWrapper>
|
|
||||||
<AdminLayout>{children}</AdminLayout>
|
|
||||||
</AuthWrapper>
|
|
||||||
</InstanceWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ImageLayout;
|
export default ImageLayout;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import useSWR from "swr";
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
import { InstanceImageConfigForm } from "./components";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// local
|
||||||
|
import { InstanceImageConfigForm } from "./form";
|
||||||
|
|
||||||
const InstanceImagePage = observer(() => {
|
const InstanceImagePage = observer(() => {
|
||||||
// store
|
// store
|
||||||
|
@ -1,48 +1,56 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { Metadata } from "next";
|
||||||
|
// components
|
||||||
|
import { InstanceFailureView, InstanceNotReady } from "@/components/instance";
|
||||||
|
// helpers
|
||||||
|
import { ASSET_PREFIX } from "@/helpers/common.helper";
|
||||||
// lib
|
// lib
|
||||||
import { StoreProvider } from "@/lib/store-context";
|
import { AppProvider } from "@/lib/app-providers";
|
||||||
import { AppWrapper } from "@/lib/wrappers";
|
|
||||||
// constants
|
|
||||||
import { SITE_NAME, SITE_DESCRIPTION, SITE_URL, TWITTER_USER_NAME, SITE_KEYWORDS, SITE_TITLE } from "@/constants/seo";
|
|
||||||
// styles
|
// styles
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
// services
|
||||||
|
import { InstanceService } from "@/services";
|
||||||
|
|
||||||
interface RootLayoutProps {
|
const instanceService = new InstanceService();
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RootLayout = ({ children, ...pageProps }: RootLayoutProps) => {
|
export const metadata: Metadata = {
|
||||||
const prefix = "/god-mode/";
|
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||||
|
description:
|
||||||
|
"Open-source project management tool to manage issues, 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 issues, 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, issue tracking, agile, scrum, kanban, collaboration",
|
||||||
|
twitter: {
|
||||||
|
site: "@planepowers",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||||
|
const instanceDetails = await instanceService.getInstanceInfo().catch(() => null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{SITE_TITLE}</title>
|
<link rel="apple-touch-icon" sizes="180x180" href={`${ASSET_PREFIX}/favicon/apple-touch-icon.png`} />
|
||||||
<meta property="og:site_name" content={SITE_NAME} />
|
<link rel="icon" type="image/png" sizes="32x32" href={`${ASSET_PREFIX}/favicon/favicon-32x32.png`} />
|
||||||
<meta property="og:title" content={SITE_TITLE} />
|
<link rel="icon" type="image/png" sizes="16x16" href={`${ASSET_PREFIX}/favicon/favicon-16x16.png`} />
|
||||||
<meta property="og:url" content={SITE_URL} />
|
<link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} />
|
||||||
<meta name="description" content={SITE_DESCRIPTION} />
|
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
|
||||||
<meta property="og:description" content={SITE_DESCRIPTION} />
|
|
||||||
<meta name="keywords" content={SITE_KEYWORDS} />
|
|
||||||
<meta name="twitter:site" content={`@${TWITTER_USER_NAME}`} />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href={`${prefix}favicon/apple-touch-icon.png`} />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href={`${prefix}favicon/favicon-32x32.png`} />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href={`${prefix}favicon/favicon-16x16.png`} />
|
|
||||||
<link rel="manifest" href={`${prefix}site.webmanifest.json`} />
|
|
||||||
<link rel="shortcut icon" href={`${prefix}favicon/favicon.ico`} />
|
|
||||||
</head>
|
</head>
|
||||||
<body className={`antialiased`}>
|
<body className={`antialiased`}>
|
||||||
<StoreProvider {...pageProps}>
|
<AppProvider initialState={{ instance: instanceDetails }}>
|
||||||
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
{instanceDetails ? (
|
||||||
<AppWrapper>{children}</AppWrapper>
|
<>{instanceDetails?.instance?.is_setup_done ? <>{children}</> : <InstanceNotReady />}</>
|
||||||
</ThemeProvider>
|
) : (
|
||||||
</StoreProvider>
|
<InstanceFailureView />
|
||||||
|
)}
|
||||||
|
</AppProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default RootLayout;
|
|
||||||
|
@ -1,26 +1,11 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
// layouts
|
|
||||||
import { DefaultLayout } from "@/layouts";
|
|
||||||
// components
|
|
||||||
import { PageHeader } from "@/components/core";
|
|
||||||
import { InstanceSignInForm } from "@/components/login";
|
import { InstanceSignInForm } from "@/components/login";
|
||||||
// lib
|
// layouts
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
import { DefaultLayout } from "@/layouts/default-layout";
|
||||||
// helpers
|
|
||||||
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
|
|
||||||
|
|
||||||
const LoginPage = () => (
|
export default async function LoginPage() {
|
||||||
<>
|
return (
|
||||||
<PageHeader title="Login - God Mode" />
|
<DefaultLayout>
|
||||||
<InstanceWrapper pageType={EInstancePageType.POST_SETUP}>
|
<InstanceSignInForm />
|
||||||
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>
|
</DefaultLayout>
|
||||||
<DefaultLayout>
|
);
|
||||||
<InstanceSignInForm />
|
}
|
||||||
</DefaultLayout>
|
|
||||||
</AuthWrapper>
|
|
||||||
</InstanceWrapper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default LoginPage;
|
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
import { FC, useEffect, useMemo, useState } from "react";
|
import { FC, useEffect, useMemo, useState } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
// services
|
// icons
|
||||||
import { AuthService } from "@/services/auth.service";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Checkbox, Input, Spinner } from "@plane/ui";
|
import { Button, Checkbox, Input, Spinner } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { Banner, PasswordStrengthMeter } from "components/common";
|
import { Banner, PasswordStrengthMeter } from "@/components/common";
|
||||||
// icons
|
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||||
|
// services
|
||||||
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
|
||||||
// service initialization
|
// service initialization
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
@ -154,7 +154,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
First name <span className="text-red-500">*</span>
|
First name <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="first_name"
|
id="first_name"
|
||||||
name="first_name"
|
name="first_name"
|
||||||
type="text"
|
type="text"
|
||||||
@ -167,10 +167,10 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full space-y-1">
|
<div className="w-full space-y-1">
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="last_name">
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="last_name">
|
||||||
Last name
|
Last name <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="last_name"
|
id="last_name"
|
||||||
name="last_name"
|
name="last_name"
|
||||||
type="text"
|
type="text"
|
||||||
@ -187,7 +187,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
Email <span className="text-red-500">*</span>
|
Email <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
@ -204,10 +204,10 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
|
|
||||||
<div className="w-full space-y-1">
|
<div className="w-full space-y-1">
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="company_name">
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="company_name">
|
||||||
Company name
|
Company name <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="company_name"
|
id="company_name"
|
||||||
name="company_name"
|
name="company_name"
|
||||||
type="text"
|
type="text"
|
||||||
@ -224,7 +224,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
@ -239,6 +239,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
{showPassword ? (
|
{showPassword ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
||||||
onClick={() => setShowPassword(false)}
|
onClick={() => setShowPassword(false)}
|
||||||
>
|
>
|
||||||
@ -247,6 +248,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
||||||
onClick={() => setShowPassword(true)}
|
onClick={() => setShowPassword(true)}
|
||||||
>
|
>
|
||||||
@ -262,7 +264,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
|
|
||||||
<div className="w-full space-y-1">
|
<div className="w-full space-y-1">
|
||||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||||
Confirm password
|
Confirm password <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
@ -278,6 +280,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
{showPassword ? (
|
{showPassword ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
||||||
onClick={() => setShowPassword(false)}
|
onClick={() => setShowPassword(false)}
|
||||||
>
|
>
|
||||||
@ -286,6 +289,7 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
|
||||||
onClick={() => setShowPassword(true)}
|
onClick={() => setShowPassword(true)}
|
||||||
>
|
>
|
||||||
@ -314,7 +318,13 @@ export const InstanceSignUpForm: FC = (props) => {
|
|||||||
>
|
>
|
||||||
Allow Plane to anonymously collect usage events.
|
Allow Plane to anonymously collect usage events.
|
||||||
</label>
|
</label>
|
||||||
<a href="https://docs.plane.so/telemetry" className="text-sm font-medium text-blue-500 hover:text-blue-600">
|
<a
|
||||||
|
tabIndex={-1}
|
||||||
|
href="https://docs.plane.so/telemetry"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-sm font-medium text-blue-500 hover:text-blue-600"
|
||||||
|
>
|
||||||
See More
|
See More
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
// lib
|
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
|
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
|
||||||
|
// lib
|
||||||
|
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||||
|
|
||||||
interface SetupLayoutProps {
|
interface SetupLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
params: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SetupLayout = ({ children }: SetupLayoutProps) => (
|
export default function SetupLayout(props: SetupLayoutProps) {
|
||||||
<InstanceWrapper pageType={EInstancePageType.PRE_SETUP}>
|
const { children, params } = props;
|
||||||
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>{children}</AuthWrapper>
|
const { error_code } = params;
|
||||||
</InstanceWrapper>
|
console.log("error_code", error_code);
|
||||||
);
|
return (
|
||||||
|
<InstanceWrapper pageType={EInstancePageType.PRE_SETUP}>
|
||||||
export default SetupLayout;
|
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>{children}</AuthWrapper>
|
||||||
|
</InstanceWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
// layouts
|
// layouts
|
||||||
import { DefaultLayout } from "@/layouts";
|
import { DefaultLayout } from "@/layouts/default-layout";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
|
||||||
import { InstanceSignUpForm } from "./components";
|
import { InstanceSignUpForm } from "./components";
|
||||||
|
|
||||||
const SetupPage = () => (
|
export const metadata: Metadata = {
|
||||||
<>
|
title: "Setup - God Mode",
|
||||||
<PageHeader title="Setup - God Mode" />
|
};
|
||||||
<DefaultLayout>
|
|
||||||
<InstanceSignUpForm />
|
|
||||||
</DefaultLayout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default SetupPage;
|
export default function SetupPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DefaultLayout>
|
||||||
|
<InstanceSignUpForm />
|
||||||
|
</DefaultLayout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useState, useRef } from "react";
|
import { FC, useState, useRef } from "react";
|
||||||
import { Transition } from "@headlessui/react";
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
|
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
|
||||||
|
import { Transition } from "@headlessui/react";
|
||||||
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useTheme } from "@/hooks";
|
import { WEB_BASE_URL } from "@/helpers/common.helper";
|
||||||
|
import { useTheme } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
import packageJson from "package.json";
|
import packageJson from "package.json";
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ const helpOptions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const HelpSection: FC = () => {
|
export const HelpSection: FC = observer(() => {
|
||||||
// states
|
// states
|
||||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||||
// store
|
// store
|
||||||
@ -36,7 +38,7 @@ export const HelpSection: FC = () => {
|
|||||||
// refs
|
// refs
|
||||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const redirectionLink = `${process.env.NEXT_PUBLIC_APP_URL ? `${process.env.NEXT_PUBLIC_APP_URL}/create-workspace` : `/god-mode/`}`;
|
const redirectionLink = encodeURI(WEB_BASE_URL + "/create-workspace");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -128,4 +130,4 @@ export const HelpSection: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
import { FC, useEffect, useRef } from "react";
|
import { FC, useEffect, useRef } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useTheme } from "@/hooks";
|
import { HelpSection, SidebarMenu, SidebarDropdown } from "@/components/admin-sidebar";
|
||||||
|
import { useTheme } from "@/hooks/store";
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
// components
|
// components
|
||||||
import { HelpSection, SidebarMenu, SidebarDropdown } from "@/components/admin-sidebar";
|
|
||||||
|
|
||||||
export interface IInstanceSidebar {}
|
export interface IInstanceSidebar {}
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { useTheme as useNextTheme } from "next-themes";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useTheme as useNextTheme } from "next-themes";
|
||||||
import { LogOut, UserCog2, Palette } from "lucide-react";
|
import { LogOut, UserCog2, Palette } from "lucide-react";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { Avatar } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useTheme, useUser } from "@/hooks";
|
|
||||||
// helpers
|
|
||||||
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||||
|
import { useTheme, useUser } from "@/hooks/store";
|
||||||
|
// helpers
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services";
|
import { AuthService } from "@/services";
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import { useTheme } from "@/hooks";
|
|
||||||
// icons
|
|
||||||
import { Menu } from "lucide-react";
|
import { Menu } from "lucide-react";
|
||||||
|
import { useTheme } from "@/hooks/store";
|
||||||
|
// icons
|
||||||
|
|
||||||
export const SidebarHamburgerToggle: FC = observer(() => {
|
export const SidebarHamburgerToggle: FC = observer(() => {
|
||||||
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
const { isSidebarCollapsed, toggleSidebar } = useTheme();
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
|
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
|
||||||
import { Tooltip } from "@plane/ui";
|
import { Tooltip } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useTheme } from "@/hooks";
|
|
||||||
// helpers
|
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
import { useTheme } from "@/hooks/store";
|
||||||
|
// helpers
|
||||||
|
|
||||||
const INSTANCE_ADMIN_LINKS = [
|
const INSTANCE_ADMIN_LINKS = [
|
||||||
{
|
{
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
// mobx
|
// mobx
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
// ui
|
// ui
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
// icons
|
// icons
|
||||||
import { Breadcrumbs } from "@plane/ui";
|
import { Breadcrumbs } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "components/common";
|
|
||||||
import { SidebarHamburgerToggle } from "@/components/admin-sidebar";
|
import { SidebarHamburgerToggle } from "@/components/admin-sidebar";
|
||||||
|
import { BreadcrumbLink } from "components/common";
|
||||||
|
|
||||||
export const InstanceHeader: FC = observer(() => {
|
export const InstanceHeader: FC = observer(() => {
|
||||||
const pathName = usePathname();
|
const pathName = usePathname();
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Controller, Control } from "react-hook-form";
|
import { Controller, Control } from "react-hook-form";
|
||||||
// ui
|
// ui
|
||||||
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
import { Input } from "@plane/ui";
|
import { Input } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
|
||||||
@ -62,6 +62,7 @@ export const ControllerInput: React.FC<Props> = (props) => {
|
|||||||
{type === "password" &&
|
{type === "password" &&
|
||||||
(showPassword ? (
|
(showPassword ? (
|
||||||
<button
|
<button
|
||||||
|
tabIndex={-1}
|
||||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||||
onClick={() => setShowPassword(false)}
|
onClick={() => setShowPassword(false)}
|
||||||
>
|
>
|
||||||
@ -69,6 +70,7 @@ export const ControllerInput: React.FC<Props> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
|
tabIndex={-1}
|
||||||
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
|
||||||
onClick={() => setShowPassword(true)}
|
onClick={() => setShowPassword(true)}
|
||||||
>
|
>
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
// ui
|
// ui
|
||||||
|
import { Copy } from "lucide-react";
|
||||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
import { Copy } from "lucide-react";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string;
|
label: string;
|
||||||
@ -40,7 +40,7 @@ export const CopyField: React.FC<Props> = (props) => {
|
|||||||
<p className="text-sm font-medium">{url}</p>
|
<p className="text-sm font-medium">{url}</p>
|
||||||
<Copy size={18} color="#B9B9B9" />
|
<Copy size={18} color="#B9B9B9" />
|
||||||
</Button>
|
</Button>
|
||||||
<p className="text-xs text-custom-text-400">{description}</p>
|
<div className="text-xs text-custom-text-400">{description}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
46
admin/components/common/empty-state.tsx
Normal file
46
admin/components/common/empty-state.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Button } from "@plane/ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
description?: React.ReactNode;
|
||||||
|
image?: any;
|
||||||
|
primaryButton?: {
|
||||||
|
icon?: any;
|
||||||
|
text: string;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
secondaryButton?: React.ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmptyState: React.FC<Props> = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
image,
|
||||||
|
primaryButton,
|
||||||
|
secondaryButton,
|
||||||
|
disabled = false,
|
||||||
|
}) => (
|
||||||
|
<div className={`flex h-full w-full items-center justify-center`}>
|
||||||
|
<div className="flex w-full flex-col items-center text-center">
|
||||||
|
{image && <Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />}
|
||||||
|
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
|
||||||
|
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{primaryButton && (
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
prependIcon={primaryButton.icon}
|
||||||
|
onClick={primaryButton.onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{primaryButton.text}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{secondaryButton}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
@ -4,3 +4,4 @@ export * from "./controller-input";
|
|||||||
export * from "./copy-field";
|
export * from "./copy-field";
|
||||||
export * from "./password-strength-meter";
|
export * from "./password-strength-meter";
|
||||||
export * from "./banner";
|
export * from "./banner";
|
||||||
|
export * from "./empty-state";
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
import { CircleCheck } from "lucide-react";
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||||
// icons
|
// icons
|
||||||
import { CircleCheck } from "lucide-react";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./instance-not-ready";
|
export * from "./instance-not-ready";
|
||||||
|
export * from "./instance-failure-view";
|
||||||
|
42
admin/components/instance/instance-failure-view.tsx
Normal file
42
admin/components/instance/instance-failure-view.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"use client";
|
||||||
|
import { FC } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Button } from "@plane/ui";
|
||||||
|
// assets
|
||||||
|
import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg";
|
||||||
|
import InstanceFailureImage from "@/public/instance/instance-failure.svg";
|
||||||
|
|
||||||
|
type InstanceFailureViewProps = {
|
||||||
|
// mutate: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InstanceFailureView: FC<InstanceFailureViewProps> = () => {
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
||||||
|
|
||||||
|
const handleRetry = () => {
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
|
||||||
|
<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">
|
||||||
|
<Image src={instanceImage} alt="Plane Logo" />
|
||||||
|
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3>
|
||||||
|
<p className="font-medium text-base text-center">
|
||||||
|
We were unable to fetch the details of the instance. <br />
|
||||||
|
Fret not, it might just be a connectivity issue.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Button size="md" onClick={handleRetry}>
|
||||||
|
Retry
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import Link from "next/link";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// assets
|
// assets
|
||||||
import PlaneTakeOffImage from "@/public/images/plane-takeoff.png";
|
import PlaneTakeOffImage from "@/public/images/plane-takeoff.png";
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
import { FC, useEffect, useMemo, useState } from "react";
|
import { FC, useEffect, useMemo, useState } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/auth.service";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
// ui
|
|
||||||
import { Button, Input, Spinner } from "@plane/ui";
|
import { Button, Input, Spinner } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { Banner } from "components/common";
|
import { Banner } from "@/components/common";
|
||||||
// icons
|
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
// ui
|
||||||
|
// icons
|
||||||
|
|
||||||
// service initialization
|
// service initialization
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
@ -57,6 +57,8 @@ export const InstanceSignInForm: FC = (props) => {
|
|||||||
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
|
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
|
||||||
setFormData((prev) => ({ ...prev, [key]: value }));
|
setFormData((prev) => ({ ...prev, [key]: value }));
|
||||||
|
|
||||||
|
console.log("csrfToken", csrfToken);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (csrfToken === undefined)
|
if (csrfToken === undefined)
|
||||||
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
|
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
|
||||||
@ -119,7 +121,7 @@ export const InstanceSignInForm: FC = (props) => {
|
|||||||
Email <span className="text-red-500">*</span>
|
Email <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
@ -137,7 +139,7 @@ export const InstanceSignInForm: FC = (props) => {
|
|||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
|
@ -8,18 +8,20 @@ import { useTheme as nextUseTheme } from "next-themes";
|
|||||||
import { Button, getButtonStyling } from "@plane/ui";
|
import { Button, getButtonStyling } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { resolveGeneralTheme } from "helpers/common.helper";
|
import { resolveGeneralTheme } from "helpers/common.helper";
|
||||||
|
// hooks
|
||||||
|
import { useInstance, useTheme } from "@/hooks/store";
|
||||||
// icons
|
// icons
|
||||||
import TakeoffIconLight from "/public/logos/takeoff-icon-light.svg";
|
import TakeoffIconLight from "/public/logos/takeoff-icon-light.svg";
|
||||||
import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
|
import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
|
||||||
import { useTheme } from "@/hooks";
|
|
||||||
|
|
||||||
export const NewUserPopup: React.FC = observer(() => {
|
export const NewUserPopup: React.FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
|
||||||
|
const { instance } = useInstance();
|
||||||
// theme
|
// theme
|
||||||
const { resolvedTheme } = nextUseTheme();
|
const { resolvedTheme } = nextUseTheme();
|
||||||
|
|
||||||
const redirectionLink = `${process.env.NEXT_PUBLIC_APP_URL ? `${process.env.NEXT_PUBLIC_APP_URL}/create-workspace` : `/god-mode/`}`;
|
const redirectionLink = `${instance?.config?.app_base_url ? `${instance?.config?.app_base_url}/create-workspace` : `/god-mode/`}`;
|
||||||
|
|
||||||
if (!isNewUserPopup) return <></>;
|
if (!isNewUserPopup) return <></>;
|
||||||
return (
|
return (
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export enum EPageTypes {
|
export enum EPageTypes {
|
||||||
"PUBLIC" = "PUBLIC",
|
PUBLIC = "PUBLIC",
|
||||||
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
|
NON_AUTHENTICATED = "NON_AUTHENTICATED",
|
||||||
"ONBOARDING" = "ONBOARDING",
|
SET_PASSWORD = "SET_PASSWORD",
|
||||||
"AUTHENTICATED" = "AUTHENTICATED",
|
ONBOARDING = "ONBOARDING",
|
||||||
|
AUTHENTICATED = "AUTHENTICATED",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EAuthModes {
|
export enum EAuthModes {
|
||||||
@ -18,28 +20,26 @@ export enum EAuthSteps {
|
|||||||
UNIQUE_CODE = "UNIQUE_CODE",
|
UNIQUE_CODE = "UNIQUE_CODE",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EAuthenticationErrorCodes {
|
|
||||||
INSTANCE_NOT_CONFIGURED = "5000",
|
|
||||||
// Admin
|
|
||||||
ADMIN_ALREADY_EXIST = "5029",
|
|
||||||
REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5030",
|
|
||||||
INVALID_ADMIN_EMAIL = "5031",
|
|
||||||
INVALID_ADMIN_PASSWORD = "5032",
|
|
||||||
REQUIRED_ADMIN_EMAIL_PASSWORD = "5033",
|
|
||||||
ADMIN_AUTHENTICATION_FAILED = "5034",
|
|
||||||
ADMIN_USER_ALREADY_EXIST = "5035",
|
|
||||||
ADMIN_USER_DOES_NOT_EXIST = "5036",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EErrorAlertType {
|
export enum EErrorAlertType {
|
||||||
BANNER_ALERT = "BANNER_ALERT",
|
BANNER_ALERT = "BANNER_ALERT",
|
||||||
TOAST_ALERT = "TOAST_ALERT",
|
|
||||||
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
|
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
|
||||||
INLINE_EMAIL = "INLINE_EMAIL",
|
INLINE_EMAIL = "INLINE_EMAIL",
|
||||||
INLINE_PASSWORD = "INLINE_PASSWORD",
|
INLINE_PASSWORD = "INLINE_PASSWORD",
|
||||||
INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE",
|
INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EAuthenticationErrorCodes {
|
||||||
|
// Admin
|
||||||
|
ADMIN_ALREADY_EXIST = "5150",
|
||||||
|
REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5155",
|
||||||
|
INVALID_ADMIN_EMAIL = "5160",
|
||||||
|
INVALID_ADMIN_PASSWORD = "5165",
|
||||||
|
REQUIRED_ADMIN_EMAIL_PASSWORD = "5170",
|
||||||
|
ADMIN_AUTHENTICATION_FAILED = "5175",
|
||||||
|
ADMIN_USER_ALREADY_EXIST = "5180",
|
||||||
|
ADMIN_USER_DOES_NOT_EXIST = "5185",
|
||||||
|
}
|
||||||
|
|
||||||
export type TAuthErrorInfo = {
|
export type TAuthErrorInfo = {
|
||||||
type: EErrorAlertType;
|
type: EErrorAlertType;
|
||||||
code: EAuthenticationErrorCodes;
|
code: EAuthenticationErrorCodes;
|
||||||
@ -50,41 +50,54 @@ export type TAuthErrorInfo = {
|
|||||||
const errorCodeMessages: {
|
const errorCodeMessages: {
|
||||||
[key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
|
[key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
|
||||||
} = {
|
} = {
|
||||||
[EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: {
|
// admin
|
||||||
title: "Instance not configured",
|
|
||||||
message: () => "Please contact your administrator to configure the instance.",
|
|
||||||
},
|
|
||||||
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
|
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
|
||||||
title: "Admin already exists",
|
title: `Admin already exists`,
|
||||||
message: () => "Admin already exists. Please sign in.",
|
message: () => `Admin already exists. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
||||||
title: "Required",
|
title: `Email, password and first name required`,
|
||||||
message: () => "Please enter email, password and first name.",
|
message: () => `Email, password and first name required. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
|
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
|
||||||
title: "Invalid email",
|
title: `Invalid admin email`,
|
||||||
message: () => "Please enter a valid email.",
|
message: () => `Invalid admin email. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
||||||
title: "Invalid password",
|
title: `Invalid admin password`,
|
||||||
message: () => "Password must be at least 8 characters long.",
|
message: () => `Invalid admin password. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
||||||
title: "Required",
|
title: `Email and password required`,
|
||||||
message: () => "Please enter email and password.",
|
message: () => `Email and password required. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
||||||
title: "Authentication failed",
|
title: `Authentication failed`,
|
||||||
message: () => "Please check your email and password and try again.",
|
message: () => `Authentication failed. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
[EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
||||||
title: "User already exists",
|
title: `Admin user already exists`,
|
||||||
message: () => "User already exists. Please sign in.",
|
message: () => (
|
||||||
|
<div>
|
||||||
|
Admin user already exists.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
||||||
title: "User does not exist",
|
title: `Admin user does not exist`,
|
||||||
message: () => "User does not exist. Please sign up.",
|
message: () => (
|
||||||
|
<div>
|
||||||
|
Admin user does not exist.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,28 +105,16 @@ export const authErrorHandler = (
|
|||||||
errorCode: EAuthenticationErrorCodes,
|
errorCode: EAuthenticationErrorCodes,
|
||||||
email?: string | undefined
|
email?: string | undefined
|
||||||
): TAuthErrorInfo | undefined => {
|
): TAuthErrorInfo | undefined => {
|
||||||
const toastAlertErrorCodes = [
|
const bannerAlertErrorCodes = [
|
||||||
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
|
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
||||||
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
|
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
|
||||||
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
|
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
|
||||||
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
|
|
||||||
];
|
|
||||||
const bannerAlertErrorCodes = [
|
|
||||||
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
|
|
||||||
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
|
||||||
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
|
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
|
||||||
|
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
|
||||||
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
|
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
|
||||||
EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
|
EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (toastAlertErrorCodes.includes(errorCode))
|
|
||||||
return {
|
|
||||||
type: EErrorAlertType.TOAST_ALERT,
|
|
||||||
code: errorCode,
|
|
||||||
title: errorCodeMessages[errorCode]?.title || "Error",
|
|
||||||
message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bannerAlertErrorCodes.includes(errorCode))
|
if (bannerAlertErrorCodes.includes(errorCode))
|
||||||
return {
|
return {
|
||||||
type: EErrorAlertType.BANNER_ALERT,
|
type: EErrorAlertType.BANNER_ALERT,
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
import { clsx, type ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ? process.env.NEXT_PUBLIC_API_BASE_URL : "";
|
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "";
|
||||||
|
|
||||||
|
export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "";
|
||||||
|
|
||||||
|
export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || "";
|
||||||
|
export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "";
|
||||||
|
|
||||||
|
export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || "";
|
||||||
|
|
||||||
|
export const ASSET_PREFIX = ADMIN_BASE_PATH;
|
||||||
|
|
||||||
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
|
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export * from "./use-outside-click-detector";
|
|
||||||
|
|
||||||
// store-hooks
|
|
||||||
export * from "./store/use-theme";
|
|
||||||
export * from "./store/use-instance";
|
|
||||||
export * from "./store/use-user";
|
|
3
admin/hooks/store/index.ts
Normal file
3
admin/hooks/store/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./use-theme";
|
||||||
|
export * from "./use-instance";
|
||||||
|
export * from "./use-user";
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
import { StoreContext } from "@/lib/app-providers";
|
||||||
import { IInstanceStore } from "@/store/instance.store";
|
import { IInstanceStore } from "@/store/instance.store";
|
||||||
|
|
||||||
export const useInstance = (): IInstanceStore => {
|
export const useInstance = (): IInstanceStore => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
import { StoreContext } from "@/lib/app-providers";
|
||||||
import { IThemeStore } from "@/store/theme.store";
|
import { IThemeStore } from "@/store/theme.store";
|
||||||
|
|
||||||
export const useTheme = (): IThemeStore => {
|
export const useTheme = (): IThemeStore => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
import { StoreContext } from "@/lib/app-providers";
|
||||||
import { IUserStore } from "@/store/user.store";
|
import { IUserStore } from "@/store/user.store";
|
||||||
|
|
||||||
export const useUser = (): IUserStore => {
|
export const useUser = (): IUserStore => {
|
||||||
|
@ -1,15 +1,48 @@
|
|||||||
import { FC, ReactNode } from "react";
|
"use client";
|
||||||
|
import { FC, ReactNode, useEffect } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import useSWR from "swr";
|
||||||
|
// ui
|
||||||
|
import { Spinner } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { InstanceSidebar } from "@/components/admin-sidebar";
|
import { InstanceSidebar } from "@/components/admin-sidebar";
|
||||||
import { InstanceHeader } from "@/components/auth-header";
|
import { InstanceHeader } from "@/components/auth-header";
|
||||||
import { NewUserPopup } from "@/components/new-user-popup";
|
import { NewUserPopup } from "@/components/new-user-popup";
|
||||||
|
// hooks
|
||||||
|
import { useInstance, useUser } from "@/hooks/store";
|
||||||
|
|
||||||
type TAdminLayout = {
|
type TAdminLayout = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdminLayout: FC<TAdminLayout> = (props) => {
|
export const AdminLayout: FC<TAdminLayout> = observer((props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
// router
|
||||||
|
const router = useRouter();
|
||||||
|
// hooks
|
||||||
|
const { fetchInstanceAdmins } = useInstance();
|
||||||
|
const { fetchCurrentUser, isUserLoggedIn } = useUser();
|
||||||
|
|
||||||
|
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
|
||||||
|
|
||||||
|
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
|
||||||
|
shouldRetryOnError: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isUserLoggedIn === false) {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
}, [router, isUserLoggedIn]);
|
||||||
|
|
||||||
|
if (isUserLoggedIn === undefined) {
|
||||||
|
return (
|
||||||
|
<div className="relative flex h-screen w-full items-center justify-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen w-screen overflow-hidden">
|
<div className="relative flex h-screen w-screen overflow-hidden">
|
||||||
@ -21,4 +54,4 @@ export const AdminLayout: FC<TAdminLayout> = (props) => {
|
|||||||
<NewUserPopup />
|
<NewUserPopup />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -17,6 +17,7 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
|
|||||||
const { children, withoutBackground = false } = props;
|
const { children, withoutBackground = false } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@ -29,11 +30,7 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
{!withoutBackground && (
|
{!withoutBackground && (
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image
|
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
|
||||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
|
||||||
className="w-screen h-full object-cover"
|
|
||||||
alt="Plane background pattern"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
|
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
export * from "./default-layout";
|
|
||||||
export * from "./admin-layout";
|
|
44
admin/lib/app-providers.tsx
Normal file
44
admin/lib/app-providers.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ReactNode, createContext } from "react";
|
||||||
|
import { ThemeProvider } from "next-themes";
|
||||||
|
// ui
|
||||||
|
import { AppWrapper } from "@/lib/wrappers";
|
||||||
|
// store
|
||||||
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
|
let rootStore = new RootStore();
|
||||||
|
|
||||||
|
export const StoreContext = createContext(rootStore);
|
||||||
|
|
||||||
|
function initializeStore(initialData = {}) {
|
||||||
|
const singletonRootStore = rootStore ?? new RootStore();
|
||||||
|
// If your page has Next.js data fetching methods that use a Mobx store, it will
|
||||||
|
// get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details
|
||||||
|
if (initialData) {
|
||||||
|
console.log("initialState", initialData);
|
||||||
|
singletonRootStore.hydrate(initialData);
|
||||||
|
}
|
||||||
|
// For SSG and SSR always create a new store
|
||||||
|
if (typeof window === "undefined") return singletonRootStore;
|
||||||
|
// Create the store once in the client
|
||||||
|
if (!rootStore) rootStore = singletonRootStore;
|
||||||
|
return singletonRootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AppProviderProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
initialState: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => {
|
||||||
|
const store = initializeStore(initialState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
|
||||||
|
<StoreContext.Provider value={store}>
|
||||||
|
<AppWrapper>{children}</AppWrapper>
|
||||||
|
</StoreContext.Provider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,21 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactElement, createContext } from "react";
|
|
||||||
// mobx store
|
|
||||||
import { RootStore } from "@/store/root-store";
|
|
||||||
|
|
||||||
export let rootStore = new RootStore();
|
|
||||||
|
|
||||||
export const StoreContext = createContext<RootStore>(rootStore);
|
|
||||||
|
|
||||||
const initializeStore = () => {
|
|
||||||
const newRootStore = rootStore ?? new RootStore();
|
|
||||||
if (typeof window === "undefined") return newRootStore;
|
|
||||||
if (!rootStore) rootStore = newRootStore;
|
|
||||||
return newRootStore;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StoreProvider = ({ children }: { children: ReactElement }) => {
|
|
||||||
const store = initializeStore();
|
|
||||||
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
|
||||||
};
|
|
@ -3,14 +3,14 @@
|
|||||||
import { FC, ReactNode, useEffect, Suspense } from "react";
|
import { FC, ReactNode, useEffect, Suspense } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { SWRConfig } from "swr";
|
import { SWRConfig } from "swr";
|
||||||
// hooks
|
|
||||||
import { useTheme, useUser } from "@/hooks";
|
|
||||||
// ui
|
// ui
|
||||||
import { Toast } from "@plane/ui";
|
import { Toast } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { SWR_CONFIG } from "constants/swr-config";
|
import { SWR_CONFIG } from "@/constants/swr-config";
|
||||||
// helpers
|
// helpers
|
||||||
import { resolveGeneralTheme } from "helpers/common.helper";
|
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
||||||
|
// hooks
|
||||||
|
import { useTheme, useUser } from "@/hooks/store";
|
||||||
|
|
||||||
interface IAppWrapper {
|
interface IAppWrapper {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance, useUser } from "@/hooks";
|
|
||||||
// helpers
|
|
||||||
import { EAuthenticationPageType } from "@/helpers";
|
import { EAuthenticationPageType } from "@/helpers";
|
||||||
|
import { useInstance, useUser } from "@/hooks/store";
|
||||||
|
// helpers
|
||||||
|
|
||||||
export interface IAuthWrapper {
|
export interface IAuthWrapper {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
import { redirect, useSearchParams } from "next/navigation";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { redirect, useSearchParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// layouts
|
|
||||||
import { DefaultLayout } from "@/layouts";
|
|
||||||
// components
|
// components
|
||||||
|
import { EmptyState } from "@/components/common";
|
||||||
import { InstanceNotReady } from "@/components/instance";
|
import { InstanceNotReady } from "@/components/instance";
|
||||||
// hooks
|
|
||||||
import { useInstance } from "@/hooks";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { EInstancePageType } from "@/helpers";
|
import { EInstancePageType } from "@/helpers";
|
||||||
|
// hooks
|
||||||
|
import { useInstance } from "@/hooks/store";
|
||||||
|
// layouts
|
||||||
|
import { DefaultLayout } from "@/layouts/default-layout";
|
||||||
|
|
||||||
type TInstanceWrapper = {
|
type TInstanceWrapper = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -28,6 +29,9 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
|
|||||||
|
|
||||||
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
|
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
errorRetryCount: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSWRLoading || isLoading)
|
if (isSWRLoading || isLoading)
|
||||||
@ -37,6 +41,15 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
return (
|
||||||
|
<EmptyState
|
||||||
|
title="Your instance wasn't configured successfully."
|
||||||
|
description="Please try re-installing Plane to fix the problem. If the issue still persists please reach out to support@plane.so."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
|
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
|
||||||
return (
|
return (
|
||||||
<DefaultLayout withoutBackground>
|
<DefaultLayout withoutBackground>
|
||||||
|
@ -7,7 +7,7 @@ const nextConfig = {
|
|||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
basePath: "/god-mode",
|
basePath: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run develop",
|
"dev": "turbo run develop",
|
||||||
"develop": "next dev --port 3333",
|
"develop": "next dev --port 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"preview": "next build && next start",
|
"preview": "next build && next start",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
@ -14,6 +14,7 @@
|
|||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/lodash": "^4.17.0",
|
"@types/lodash": "^4.17.0",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
@ -25,7 +26,7 @@
|
|||||||
"mobx-react-lite": "^4.0.5",
|
"mobx-react-lite": "^4.0.5",
|
||||||
"next": "^14.2.3",
|
"next": "^14.2.3",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"postcss": "8.4.23",
|
"postcss": "^8.4.38",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.51.0",
|
"react-hook-form": "^7.51.0",
|
||||||
|
40
admin/public/instance/instance-failure-dark.svg
Normal file
40
admin/public/instance/instance-failure-dark.svg
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<svg width="210" height="206" viewBox="0 0 210 206" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="107.5" cy="103" r="102.5" fill="#24252C"/>
|
||||||
|
<path d="M140.625 162.125V148.875C138.868 148.875 137.183 148.177 135.94 146.935C134.698 145.692 134 144.007 134 142.25V135.625C134 132.111 135.396 128.741 137.881 126.256C140.366 123.771 143.736 122.375 147.25 122.375H160.5C164.014 122.375 167.384 123.771 169.869 126.256C172.354 128.741 173.75 132.111 173.75 135.625V142.25C173.75 144.007 173.052 145.692 171.81 146.935C170.567 148.177 168.882 148.875 167.125 148.875" stroke="#454961" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M153.875 122.375V66.0625C153.875 59.9128 151.432 54.015 147.084 49.6665C142.735 45.318 136.837 42.875 130.687 42.875C124.538 42.875 118.64 45.318 114.291 49.6665C109.943 54.015 107.5 59.9128 107.5 66.0625M107.5 138.937C107.5 145.087 105.057 150.985 100.709 155.334C96.36 159.682 90.4622 162.125 84.3125 162.125C78.1628 162.125 72.265 159.682 67.9165 155.334C63.568 150.985 61.125 145.087 61.125 138.937V82.625" stroke="#454961" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M167.125 162.125V148.875H140.625" stroke="#454961" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M47.875 56.125H74.375V42.875" stroke="#454961" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M74.375 56.125C76.1321 56.125 77.8172 56.823 79.0596 58.0654C80.302 59.3078 81 60.9929 81 62.75V69.375C81 72.8891 79.604 76.2593 77.1192 78.7442C74.6343 81.229 71.2641 82.625 67.75 82.625H54.5C50.9859 82.625 47.6157 81.229 45.1308 78.7442C42.646 76.2593 41.25 72.8891 41.25 69.375V62.75C41.25 60.9929 41.948 59.3078 43.1904 58.0654C44.4328 56.823 46.1179 56.125 47.875 56.125V42.875" stroke="#454961" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<g filter="url(#filter0_ddd_11437_265561)">
|
||||||
|
<circle cx="107.911" cy="102.911" r="23.7938" fill="#3A5BC7"/>
|
||||||
|
<path d="M114.051 96.7712L101.771 109.052" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M101.771 96.7712L114.051 109.052" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_ddd_11437_265561" x="76.1172" y="74.1177" width="63.5879" height="64.5876" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feMorphology radius="2" operator="erode" in="SourceAlpha" result="effect1_dropShadow_11437_265561"/>
|
||||||
|
<feOffset dy="2"/>
|
||||||
|
<feGaussianBlur stdDeviation="1.5"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0 0 0 0.051 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11437_265561"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect2_dropShadow_11437_265561"/>
|
||||||
|
<feOffset dy="3"/>
|
||||||
|
<feGaussianBlur stdDeviation="6"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.055 0"/>
|
||||||
|
<feBlend mode="normal" in2="effect1_dropShadow_11437_265561" result="effect2_dropShadow_11437_265561"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feMorphology radius="8" operator="erode" in="SourceAlpha" result="effect3_dropShadow_11437_265561"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="8"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.078 0"/>
|
||||||
|
<feBlend mode="normal" in2="effect2_dropShadow_11437_265561" result="effect3_dropShadow_11437_265561"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_11437_265561" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
40
admin/public/instance/instance-failure.svg
Normal file
40
admin/public/instance/instance-failure.svg
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<svg width="210" height="206" viewBox="0 0 210 206" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="107.5" cy="103" r="102.5" fill="#F3F6FF"/>
|
||||||
|
<path d="M140.625 162.125V148.875C138.868 148.875 137.183 148.177 135.94 146.935C134.698 145.692 134 144.007 134 142.25V135.625C134 132.111 135.396 128.741 137.881 126.256C140.366 123.771 143.736 122.375 147.25 122.375H160.5C164.014 122.375 167.384 123.771 169.869 126.256C172.354 128.741 173.75 132.111 173.75 135.625V142.25C173.75 144.007 173.052 145.692 171.81 146.935C170.567 148.177 168.882 148.875 167.125 148.875" stroke="#3E63DD" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M153.875 122.375V66.0625C153.875 59.9128 151.432 54.015 147.084 49.6665C142.735 45.318 136.837 42.875 130.687 42.875C124.538 42.875 118.64 45.318 114.291 49.6665C109.943 54.015 107.5 59.9128 107.5 66.0625M107.5 138.937C107.5 145.087 105.057 150.985 100.709 155.334C96.36 159.682 90.4622 162.125 84.3125 162.125C78.1628 162.125 72.265 159.682 67.9165 155.334C63.568 150.985 61.125 145.087 61.125 138.937V82.625" stroke="#3E63DD" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M167.125 162.125V148.875H140.625" stroke="#3E63DD" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M47.875 56.125H74.375V42.875" stroke="#3E63DD" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M74.375 56.125C76.1321 56.125 77.8172 56.823 79.0596 58.0654C80.302 59.3078 81 60.9929 81 62.75V69.375C81 72.8891 79.604 76.2593 77.1192 78.7442C74.6343 81.229 71.2641 82.625 67.75 82.625H54.5C50.9859 82.625 47.6157 81.229 45.1308 78.7442C42.646 76.2593 41.25 72.8891 41.25 69.375V62.75C41.25 60.9929 41.948 59.3078 43.1904 58.0654C44.4328 56.823 46.1179 56.125 47.875 56.125V42.875" stroke="#3E63DD" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<g filter="url(#filter0_ddd_11424_265422)">
|
||||||
|
<circle cx="107.911" cy="102.911" r="23.7938" fill="#3A5BC7"/>
|
||||||
|
<path d="M114.051 96.7712L101.771 109.052" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M101.771 96.7712L114.051 109.052" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_ddd_11424_265422" x="76.1172" y="74.1177" width="63.5879" height="64.5876" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feMorphology radius="2" operator="erode" in="SourceAlpha" result="effect1_dropShadow_11424_265422"/>
|
||||||
|
<feOffset dy="2"/>
|
||||||
|
<feGaussianBlur stdDeviation="1.5"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0 0 0 0.051 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11424_265422"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect2_dropShadow_11424_265422"/>
|
||||||
|
<feOffset dy="3"/>
|
||||||
|
<feGaussianBlur stdDeviation="6"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.055 0"/>
|
||||||
|
<feBlend mode="normal" in2="effect1_dropShadow_11424_265422" result="effect2_dropShadow_11424_265422"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feMorphology radius="8" operator="erode" in="SourceAlpha" result="effect3_dropShadow_11424_265422"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="8"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.078 0"/>
|
||||||
|
<feBlend mode="normal" in2="effect2_dropShadow_11424_265422" result="effect3_dropShadow_11424_265422"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_11424_265422" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
BIN
admin/public/instance/plane-instance-not-ready.webp
Normal file
BIN
admin/public/instance/plane-instance-not-ready.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
BIN
admin/public/instance/plane-takeoff.png
Normal file
BIN
admin/public/instance/plane-takeoff.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
@ -1,6 +1,6 @@
|
|||||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
// store
|
// store
|
||||||
import { rootStore } from "@/lib/store-context";
|
// import { rootStore } from "@/lib/store-context";
|
||||||
|
|
||||||
export abstract class APIService {
|
export abstract class APIService {
|
||||||
protected baseURL: string;
|
protected baseURL: string;
|
||||||
@ -17,14 +17,14 @@ export abstract class APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupInterceptors() {
|
private setupInterceptors() {
|
||||||
this.axiosInstance.interceptors.response.use(
|
// this.axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
// (response) => response,
|
||||||
(error) => {
|
// (error) => {
|
||||||
const store = rootStore;
|
// const store = rootStore;
|
||||||
if (error.response && error.response.status === 401 && store.user.currentUser) store.user.reset();
|
// if (error.response && error.response.status === 401 && store.user.currentUser) store.user.reset();
|
||||||
return Promise.reject(error);
|
// return Promise.reject(error);
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
get<ResponseType>(url: string, params = {}): Promise<AxiosResponse<ResponseType>> {
|
get<ResponseType>(url: string, params = {}): Promise<AxiosResponse<ResponseType>> {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// services
|
|
||||||
import { APIService } from "services/api.service";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
|
// services
|
||||||
|
import { APIService } from "services/api.service";
|
||||||
|
|
||||||
type TCsrfTokenResponse = {
|
type TCsrfTokenResponse = {
|
||||||
csrf_token: string;
|
csrf_token: string;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { APIService } from "services/api.service";
|
|
||||||
// types
|
// types
|
||||||
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
|
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
|
import { APIService } from "@/services/api.service";
|
||||||
|
|
||||||
export class InstanceService extends APIService {
|
export class InstanceService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -13,6 +13,7 @@ export class InstanceService extends APIService {
|
|||||||
return this.get<IInstance>("/api/instances/")
|
return this.get<IInstance>("/api/instances/")
|
||||||
.then((response) => response.data)
|
.then((response) => response.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log("error", error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
|
// helpers
|
||||||
|
import { API_BASE_URL } from "helpers/common.helper";
|
||||||
// services
|
// services
|
||||||
import { APIService } from "services/api.service";
|
import { APIService } from "services/api.service";
|
||||||
// types
|
// types
|
||||||
import type { IUser } from "@plane/types";
|
import type { IUser } from "@plane/types";
|
||||||
// helpers
|
|
||||||
import { API_BASE_URL } from "helpers/common.helper";
|
interface IUserSession extends IUser {
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class UserService extends APIService {
|
export class UserService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(API_BASE_URL);
|
super(API_BASE_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async authCheck(): Promise<IUserSession> {
|
||||||
|
return this.get<any>("/api/instances/admins/me/")
|
||||||
|
.then((response) => ({ ...response?.data, isAuthenticated: true }))
|
||||||
|
.catch(() => ({ isAuthenticated: false }));
|
||||||
|
}
|
||||||
|
|
||||||
async currentUser(): Promise<IUser> {
|
async currentUser(): Promise<IUser> {
|
||||||
return this.get<IUser>("/api/instances/admins/me/")
|
return this.get<IUser>("/api/instances/admins/me/")
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
|
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||||
import { IInstance, IInstanceAdmin, IInstanceConfiguration, IFormattedInstanceConfiguration } from "@plane/types";
|
import { IInstance, IInstanceAdmin, IInstanceConfiguration, IFormattedInstanceConfiguration } from "@plane/types";
|
||||||
// helpers
|
// helpers
|
||||||
import { EInstanceStatus, TInstanceStatus } from "@/helpers";
|
import { EInstanceStatus, TInstanceStatus } from "@/helpers";
|
||||||
// services
|
// services
|
||||||
import { InstanceService } from "@/services/instance.service";
|
import { InstanceService } from "@/services/instance.service";
|
||||||
// root store
|
// root store
|
||||||
import { RootStore } from "@/store/root-store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
export interface IInstanceStore {
|
export interface IInstanceStore {
|
||||||
// issues
|
// issues
|
||||||
@ -18,11 +18,12 @@ export interface IInstanceStore {
|
|||||||
// computed
|
// computed
|
||||||
formattedConfig: IFormattedInstanceConfiguration | undefined;
|
formattedConfig: IFormattedInstanceConfiguration | undefined;
|
||||||
// action
|
// action
|
||||||
|
hydrate: (data: any) => void;
|
||||||
fetchInstanceInfo: () => Promise<IInstance | undefined>;
|
fetchInstanceInfo: () => Promise<IInstance | undefined>;
|
||||||
updateInstanceInfo: (data: Partial<IInstance["instance"]>) => Promise<IInstance["instance"] | undefined>;
|
updateInstanceInfo: (data: Partial<IInstance["instance"]>) => Promise<IInstance["instance"] | undefined>;
|
||||||
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
|
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
|
||||||
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
|
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
|
||||||
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<void>;
|
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<IInstanceConfiguration[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InstanceStore implements IInstanceStore {
|
export class InstanceStore implements IInstanceStore {
|
||||||
@ -45,6 +46,7 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
// computed
|
// computed
|
||||||
formattedConfig: computed,
|
formattedConfig: computed,
|
||||||
// actions
|
// actions
|
||||||
|
hydrate: action,
|
||||||
fetchInstanceInfo: action,
|
fetchInstanceInfo: action,
|
||||||
fetchInstanceAdmins: action,
|
fetchInstanceAdmins: action,
|
||||||
updateInstanceInfo: action,
|
updateInstanceInfo: action,
|
||||||
@ -55,6 +57,10 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
this.instanceService = new InstanceService();
|
this.instanceService = new InstanceService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hydrate = (data: any) => {
|
||||||
|
if (data) this.instance = data;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* computed value for instance configurations data for forms.
|
* computed value for instance configurations data for forms.
|
||||||
* @returns configurations in the form of {key, value} pair.
|
* @returns configurations in the form of {key, value} pair.
|
||||||
@ -148,13 +154,15 @@ export class InstanceStore implements IInstanceStore {
|
|||||||
*/
|
*/
|
||||||
updateInstanceConfigurations = async (data: Partial<IFormattedInstanceConfiguration>) => {
|
updateInstanceConfigurations = async (data: Partial<IFormattedInstanceConfiguration>) => {
|
||||||
try {
|
try {
|
||||||
await this.instanceService.updateInstanceConfigurations(data).then((response) => {
|
const response = await this.instanceService.updateInstanceConfigurations(data);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.instanceConfigurations = this.instanceConfigurations
|
this.instanceConfigurations = this.instanceConfigurations?.map((config) => {
|
||||||
? [...this.instanceConfigurations, ...response]
|
const item = response.find((item) => item.key === config.key);
|
||||||
: response;
|
if (item) return item;
|
||||||
|
return config;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating the instance configurations");
|
console.error("Error updating the instance configurations");
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { enableStaticRendering } from "mobx-react-lite";
|
import { enableStaticRendering } from "mobx-react-lite";
|
||||||
// stores
|
// stores
|
||||||
import { IThemeStore, ThemeStore } from "./theme.store";
|
|
||||||
import { IInstanceStore, InstanceStore } from "./instance.store";
|
import { IInstanceStore, InstanceStore } from "./instance.store";
|
||||||
|
import { IThemeStore, ThemeStore } from "./theme.store";
|
||||||
import { IUserStore, UserStore } from "./user.store";
|
import { IUserStore, UserStore } from "./user.store";
|
||||||
|
|
||||||
enableStaticRendering(typeof window === "undefined");
|
enableStaticRendering(typeof window === "undefined");
|
||||||
@ -17,9 +17,14 @@ export class RootStore {
|
|||||||
this.user = new UserStore(this);
|
this.user = new UserStore(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hydrate(initialData: any) {
|
||||||
|
this.theme.hydrate(initialData.theme);
|
||||||
|
this.instance.hydrate(initialData.instance);
|
||||||
|
this.user.hydrate(initialData.user);
|
||||||
|
}
|
||||||
|
|
||||||
resetOnSignOut() {
|
resetOnSignOut() {
|
||||||
localStorage.setItem("theme", "system");
|
localStorage.setItem("theme", "system");
|
||||||
|
|
||||||
this.instance = new InstanceStore(this);
|
this.instance = new InstanceStore(this);
|
||||||
this.user = new UserStore(this);
|
this.user = new UserStore(this);
|
||||||
this.theme = new ThemeStore(this);
|
this.theme = new ThemeStore(this);
|
@ -1,6 +1,6 @@
|
|||||||
import { action, observable, makeObservable } from "mobx";
|
import { action, observable, makeObservable } from "mobx";
|
||||||
// root store
|
// root store
|
||||||
import { RootStore } from "@/store/root-store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
type TTheme = "dark" | "light";
|
type TTheme = "dark" | "light";
|
||||||
export interface IThemeStore {
|
export interface IThemeStore {
|
||||||
@ -9,6 +9,7 @@ export interface IThemeStore {
|
|||||||
theme: string | undefined;
|
theme: string | undefined;
|
||||||
isSidebarCollapsed: boolean | undefined;
|
isSidebarCollapsed: boolean | undefined;
|
||||||
// actions
|
// actions
|
||||||
|
hydrate: (data: any) => void;
|
||||||
toggleNewUserPopup: () => void;
|
toggleNewUserPopup: () => void;
|
||||||
toggleSidebar: (collapsed: boolean) => void;
|
toggleSidebar: (collapsed: boolean) => void;
|
||||||
setTheme: (currentTheme: TTheme) => void;
|
setTheme: (currentTheme: TTheme) => void;
|
||||||
@ -33,6 +34,10 @@ export class ThemeStore implements IThemeStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hydrate = (data: any) => {
|
||||||
|
if (data) this.theme = data;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Toggle the new user popup modal
|
* @description Toggle the new user popup modal
|
||||||
*/
|
*/
|
||||||
|
@ -3,10 +3,10 @@ import { IUser } from "@plane/types";
|
|||||||
// helpers
|
// helpers
|
||||||
import { EUserStatus, TUserStatus } from "@/helpers";
|
import { EUserStatus, TUserStatus } from "@/helpers";
|
||||||
// services
|
// services
|
||||||
|
import { AuthService } from "@/services";
|
||||||
import { UserService } from "@/services/user.service";
|
import { UserService } from "@/services/user.service";
|
||||||
// root store
|
// root store
|
||||||
import { RootStore } from "@/store/root-store";
|
import { RootStore } from "@/store/root.store";
|
||||||
import { AuthService } from "@/services";
|
|
||||||
|
|
||||||
export interface IUserStore {
|
export interface IUserStore {
|
||||||
// observables
|
// observables
|
||||||
@ -15,6 +15,7 @@ export interface IUserStore {
|
|||||||
isUserLoggedIn: boolean | undefined;
|
isUserLoggedIn: boolean | undefined;
|
||||||
currentUser: IUser | undefined;
|
currentUser: IUser | undefined;
|
||||||
// fetch actions
|
// fetch actions
|
||||||
|
hydrate: (data: any) => void;
|
||||||
fetchCurrentUser: () => Promise<IUser>;
|
fetchCurrentUser: () => Promise<IUser>;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
signOut: () => void;
|
signOut: () => void;
|
||||||
@ -46,6 +47,10 @@ export class UserStore implements IUserStore {
|
|||||||
this.authService = new AuthService();
|
this.authService = new AuthService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hydrate = (data: any) => {
|
||||||
|
if (data) this.currentUser = data;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Fetches the current user
|
* @description Fetches the current user
|
||||||
* @returns Promise<IUser>
|
* @returns Promise<IUser>
|
||||||
|
@ -44,3 +44,8 @@ WEB_URL="http://localhost"
|
|||||||
|
|
||||||
# Gunicorn Workers
|
# Gunicorn Workers
|
||||||
GUNICORN_WORKERS=2
|
GUNICORN_WORKERS=2
|
||||||
|
|
||||||
|
# Base URLs
|
||||||
|
ADMIN_BASE_URL=
|
||||||
|
SPACE_BASE_URL=
|
||||||
|
APP_BASE_URL=
|
||||||
|
@ -315,7 +315,7 @@ class IssueLinkSerializer(BaseSerializer):
|
|||||||
if IssueLink.objects.filter(
|
if IssueLink.objects.filter(
|
||||||
url=validated_data.get("url"),
|
url=validated_data.get("url"),
|
||||||
issue_id=instance.issue_id,
|
issue_id=instance.issue_id,
|
||||||
).exists():
|
).exclude(pk=instance.id).exists():
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{"error": "URL already exists for this Issue"}
|
{"error": "URL already exists for this Issue"}
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
|
@ -462,7 +462,7 @@ class IssueLinkSerializer(BaseSerializer):
|
|||||||
if IssueLink.objects.filter(
|
if IssueLink.objects.filter(
|
||||||
url=validated_data.get("url"),
|
url=validated_data.get("url"),
|
||||||
issue_id=instance.issue_id,
|
issue_id=instance.issue_id,
|
||||||
).exists():
|
).exclude(pk=instance.id).exists():
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{"error": "URL already exists for this Issue"}
|
{"error": "URL already exists for this Issue"}
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@ from plane.app.views import (
|
|||||||
UserEndpoint,
|
UserEndpoint,
|
||||||
UserIssueCompletedGraphEndpoint,
|
UserIssueCompletedGraphEndpoint,
|
||||||
UserWorkspaceDashboardEndpoint,
|
UserWorkspaceDashboardEndpoint,
|
||||||
|
UserSessionEndpoint,
|
||||||
## End User
|
## End User
|
||||||
## Workspaces
|
## Workspaces
|
||||||
UserWorkSpacesEndpoint,
|
UserWorkSpacesEndpoint,
|
||||||
@ -29,6 +30,11 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="users",
|
name="users",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"users/session/",
|
||||||
|
UserSessionEndpoint.as_view(),
|
||||||
|
name="user-session",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"users/me/settings/",
|
"users/me/settings/",
|
||||||
UserEndpoint.as_view(
|
UserEndpoint.as_view(
|
||||||
|
@ -223,4 +223,4 @@ from .error_404 import custom_404_view
|
|||||||
|
|
||||||
from .exporter.base import ExportIssuesEndpoint
|
from .exporter.base import ExportIssuesEndpoint
|
||||||
from .notification.base import MarkAllReadNotificationViewSet
|
from .notification.base import MarkAllReadNotificationViewSet
|
||||||
from .user.base import AccountEndpoint, ProfileEndpoint
|
from .user.base import AccountEndpoint, ProfileEndpoint, UserSessionEndpoint
|
||||||
|
@ -241,6 +241,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"backlog_issues",
|
"backlog_issues",
|
||||||
"assignee_ids",
|
"assignee_ids",
|
||||||
"status",
|
"status",
|
||||||
|
"created_by"
|
||||||
)
|
)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
@ -365,6 +366,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"backlog_issues",
|
"backlog_issues",
|
||||||
"assignee_ids",
|
"assignee_ids",
|
||||||
"status",
|
"status",
|
||||||
|
"created_by",
|
||||||
)
|
)
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@ -564,6 +566,7 @@ class CycleViewSet(BaseViewSet):
|
|||||||
"backlog_issues",
|
"backlog_issues",
|
||||||
"assignee_ids",
|
"assignee_ids",
|
||||||
"status",
|
"status",
|
||||||
|
"created_by",
|
||||||
)
|
)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ from django.utils import timezone
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
@ -180,6 +181,25 @@ class UserEndpoint(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSessionEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
AllowAny,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
user = User.objects.get(pk=request.user.id)
|
||||||
|
serializer = UserMeSerializer(user)
|
||||||
|
data = {"is_authenticated": True}
|
||||||
|
data["user"] = serializer.data
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{"is_authenticated": False}, status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
@invalidate_cache(path="/api/users/me/")
|
@invalidate_cache(path="/api/users/me/")
|
||||||
|
@ -96,6 +96,7 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
|
|
||||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
try:
|
try:
|
||||||
serializer = WorkSpaceSerializer(data=request.data)
|
serializer = WorkSpaceSerializer(data=request.data)
|
||||||
@ -151,8 +152,12 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
return super().partial_update(request, *args, **kwargs)
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
@invalidate_cache(path="/api/users/me/workspaces/", multiple=True, user=False)
|
@invalidate_cache(
|
||||||
@invalidate_cache(path="/api/users/me/settings/", multiple=True, user=False)
|
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||||
|
)
|
||||||
|
@invalidate_cache(
|
||||||
|
path="/api/users/me/settings/", multiple=True, user=False
|
||||||
|
)
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
return super().destroy(request, *args, **kwargs)
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -100,6 +100,12 @@ class Adapter:
|
|||||||
user.save()
|
user.save()
|
||||||
Profile.objects.create(user=user)
|
Profile.objects.create(user=user)
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise AuthenticationException(
|
||||||
|
AUTHENTICATION_ERROR_CODES["USER_ACCOUNT_DEACTIVATED"],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
|
||||||
# Update user details
|
# Update user details
|
||||||
user.last_login_medium = self.provider
|
user.last_login_medium = self.provider
|
||||||
user.last_active = timezone.now()
|
user.last_active = timezone.now()
|
||||||
|
@ -1,51 +1,57 @@
|
|||||||
AUTHENTICATION_ERROR_CODES = {
|
AUTHENTICATION_ERROR_CODES = {
|
||||||
# Global
|
# Global
|
||||||
"INSTANCE_NOT_CONFIGURED": 5000,
|
"INSTANCE_NOT_CONFIGURED": 5000,
|
||||||
"INVALID_EMAIL": 5012,
|
"INVALID_EMAIL": 5005,
|
||||||
"EMAIL_REQUIRED": 5013,
|
"EMAIL_REQUIRED": 5010,
|
||||||
"SIGNUP_DISABLED": 5001,
|
"SIGNUP_DISABLED": 5015,
|
||||||
|
"MAGIC_LINK_LOGIN_DISABLED": 5016,
|
||||||
|
"PASSWORD_LOGIN_DISABLED": 5018,
|
||||||
|
"USER_ACCOUNT_DEACTIVATED": 5019,
|
||||||
# Password strength
|
# Password strength
|
||||||
"INVALID_PASSWORD": 5002,
|
"INVALID_PASSWORD": 5020,
|
||||||
"SMTP_NOT_CONFIGURED": 5007,
|
"SMTP_NOT_CONFIGURED": 5025,
|
||||||
# Sign Up
|
# Sign Up
|
||||||
"USER_ALREADY_EXIST": 5003,
|
"USER_ALREADY_EXIST": 5030,
|
||||||
"AUTHENTICATION_FAILED_SIGN_UP": 5006,
|
"AUTHENTICATION_FAILED_SIGN_UP": 5035,
|
||||||
"REQUIRED_EMAIL_PASSWORD_SIGN_UP": 5015,
|
"REQUIRED_EMAIL_PASSWORD_SIGN_UP": 5040,
|
||||||
"INVALID_EMAIL_SIGN_UP": 5017,
|
"INVALID_EMAIL_SIGN_UP": 5045,
|
||||||
"INVALID_EMAIL_MAGIC_SIGN_UP": 5019,
|
"INVALID_EMAIL_MAGIC_SIGN_UP": 5050,
|
||||||
"MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED": 5023,
|
"MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED": 5055,
|
||||||
# Sign In
|
# Sign In
|
||||||
"USER_DOES_NOT_EXIST": 5004,
|
"USER_DOES_NOT_EXIST": 5060,
|
||||||
"AUTHENTICATION_FAILED_SIGN_IN": 5005,
|
"AUTHENTICATION_FAILED_SIGN_IN": 5065,
|
||||||
"REQUIRED_EMAIL_PASSWORD_SIGN_IN": 5014,
|
"REQUIRED_EMAIL_PASSWORD_SIGN_IN": 5070,
|
||||||
"INVALID_EMAIL_SIGN_IN": 5016,
|
"INVALID_EMAIL_SIGN_IN": 5075,
|
||||||
"INVALID_EMAIL_MAGIC_SIGN_IN": 5018,
|
"INVALID_EMAIL_MAGIC_SIGN_IN": 5080,
|
||||||
"MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED": 5022,
|
"MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED": 5085,
|
||||||
# Both Sign in and Sign up
|
# Both Sign in and Sign up for magic
|
||||||
"INVALID_MAGIC_CODE": 5008,
|
"INVALID_MAGIC_CODE": 5090,
|
||||||
"EXPIRED_MAGIC_CODE": 5009,
|
"EXPIRED_MAGIC_CODE": 5095,
|
||||||
|
"EMAIL_CODE_ATTEMPT_EXHAUSTED": 5100,
|
||||||
# Oauth
|
# Oauth
|
||||||
"GOOGLE_NOT_CONFIGURED": 5010,
|
"GOOGLE_NOT_CONFIGURED": 5105,
|
||||||
"GITHUB_NOT_CONFIGURED": 5011,
|
"GITHUB_NOT_CONFIGURED": 5110,
|
||||||
"GOOGLE_OAUTH_PROVIDER_ERROR": 5021,
|
"GOOGLE_OAUTH_PROVIDER_ERROR": 5115,
|
||||||
"GITHUB_OAUTH_PROVIDER_ERROR": 5020,
|
"GITHUB_OAUTH_PROVIDER_ERROR": 5120,
|
||||||
# Reset Password
|
# Reset Password
|
||||||
"INVALID_PASSWORD_TOKEN": 5024,
|
"INVALID_PASSWORD_TOKEN": 5125,
|
||||||
"EXPIRED_PASSWORD_TOKEN": 5025,
|
"EXPIRED_PASSWORD_TOKEN": 5130,
|
||||||
# Change password
|
# Change password
|
||||||
"INCORRECT_OLD_PASSWORD": 5026,
|
"INCORRECT_OLD_PASSWORD": 5135,
|
||||||
"INVALID_NEW_PASSWORD": 5027,
|
"MISSING_PASSWORD": 5138,
|
||||||
|
"INVALID_NEW_PASSWORD": 5140,
|
||||||
# set passowrd
|
# set passowrd
|
||||||
"PASSWORD_ALREADY_SET": 5028,
|
"PASSWORD_ALREADY_SET": 5145,
|
||||||
# Admin
|
# Admin
|
||||||
"ADMIN_ALREADY_EXIST": 5029,
|
"ADMIN_ALREADY_EXIST": 5150,
|
||||||
"REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME": 5030,
|
"REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME": 5155,
|
||||||
"INVALID_ADMIN_EMAIL": 5031,
|
"INVALID_ADMIN_EMAIL": 5160,
|
||||||
"INVALID_ADMIN_PASSWORD": 5032,
|
"INVALID_ADMIN_PASSWORD": 5165,
|
||||||
"REQUIRED_ADMIN_EMAIL_PASSWORD": 5033,
|
"REQUIRED_ADMIN_EMAIL_PASSWORD": 5170,
|
||||||
"ADMIN_AUTHENTICATION_FAILED": 5034,
|
"ADMIN_AUTHENTICATION_FAILED": 5175,
|
||||||
"ADMIN_USER_ALREADY_EXIST": 5035,
|
"ADMIN_USER_ALREADY_EXIST": 5180,
|
||||||
"ADMIN_USER_DOES_NOT_EXIST": 5036,
|
"ADMIN_USER_DOES_NOT_EXIST": 5185,
|
||||||
|
"ADMIN_USER_DEACTIVATED": 5190,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.authentication.adapter.credential import CredentialAdapter
|
from plane.authentication.adapter.credential import CredentialAdapter
|
||||||
from plane.db.models import User
|
from plane.db.models import User
|
||||||
@ -5,6 +8,7 @@ from plane.authentication.adapter.error import (
|
|||||||
AUTHENTICATION_ERROR_CODES,
|
AUTHENTICATION_ERROR_CODES,
|
||||||
AuthenticationException,
|
AuthenticationException,
|
||||||
)
|
)
|
||||||
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
class EmailProvider(CredentialAdapter):
|
class EmailProvider(CredentialAdapter):
|
||||||
@ -23,6 +27,21 @@ class EmailProvider(CredentialAdapter):
|
|||||||
self.code = code
|
self.code = code
|
||||||
self.is_signup = is_signup
|
self.is_signup = is_signup
|
||||||
|
|
||||||
|
(ENABLE_EMAIL_PASSWORD,) = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "ENABLE_EMAIL_PASSWORD",
|
||||||
|
"default": os.environ.get("ENABLE_EMAIL_PASSWORD"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if ENABLE_EMAIL_PASSWORD == "0":
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES["ENABLE_EMAIL_PASSWORD"],
|
||||||
|
error_message="ENABLE_EMAIL_PASSWORD",
|
||||||
|
)
|
||||||
|
|
||||||
def set_user_data(self):
|
def set_user_data(self):
|
||||||
if self.is_signup:
|
if self.is_signup:
|
||||||
# Check if the user already exists
|
# Check if the user already exists
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user