Compare commits

...
Author SHA1 Message Date
pablohashescobar 62f9be1aaa dev: update the magic login expire check 2024-05-16 16:27:00 +05:30
pablohashescobar 3a907677c6 Merge branch 'fix-session-save' of github.com:makeplane/plane into fix-session-save 2024-05-16 16:07:28 +05:30
pablohashescobar b3ba55c1c0 dev: nginx configuration 2024-05-16 16:05:54 +05:30
sriram veeraghanta fdc22b28c7 fix: adding save every request django session 2024-05-16 16:02:01 +05:30
pablohashescobar 2b8437714c dev: update session cookie age to environment variable 2024-05-16 14:13:31 +05:30
pablohashescobar 715ad1320c dev: fix session token save on admin and remove session save every request 2024-05-16 11:58:15 +05:30
Anmol Singh BhatiaandGitHub 92c5ccef3d fix: my activity user image overflow (#4469) 2024-05-16 05:09:33 +05:30
sriram veeraghanta 1fac702096 fix: build errors 2024-05-16 05:01:14 +05:30
Anmol Singh BhatiaandGitHub 3bfe0950eb chore: custom theme input placeholder improvement (#4472) 2024-05-16 04:39:26 +05:30
Anmol Singh BhatiaandGitHub 68faced79d chore: no issues found dark mode asset updated (#4466) 2024-05-16 04:35:35 +05:30
Aaryan KhandelwalandGitHub 12cd22bba0 chore: sign out after deactivating account (#4476) 2024-05-16 04:24:50 +05:30
sriram veeraghanta a195f1bf7e fix: space user validation check 2024-05-16 04:03:43 +05:30
Bavisetti NarayanandGitHub b14d44049c [WEB-1328] chore: magic sign-in redirection (#4470)
* chore: magic signin redirection

* chore: expired magic code error message
2024-05-15 22:10:47 +05:30
sriram veeraghanta 0b84142dce Merge branch 'preview' of github.com:makeplane/plane into preview 2024-05-15 22:09:48 +05:30
sriram veeraghanta 7714825bab fix: adding new spinner 2024-05-15 22:09:16 +05:30
NikhilandGitHub 89f2e37b14 [WEB - 1315] fix: user sign up and sign in on a deactivated account (#4460)
* dev: remove email host user and email host password

* dev: fix user account deactivation error

* dev: fix caching issue of last workspace

* dev: add exclude for instances endpoint

* dev: update url redirection for auth
2024-05-15 22:08:54 +05:30
sriram veeraghanta b78a064305 refactor: admin and added new spinner 2024-05-15 21:26:57 +05:30
Anmol Singh BhatiaandGitHub 5ccb4f7d19 [WEB-1324] chore: change password page improvement (#4462)
* chore: change password page improvement

* chore: confirm password input improvement
2024-05-15 19:11:31 +05:30
Anmol Singh BhatiaandGitHub 061d52727e chore: project analytics improvement (#4457) 2024-05-15 18:43:07 +05:30
rahulrameshaandGitHub 69c9ae212b fix profile issues filter (#4461) 2024-05-15 18:42:43 +05:30
sriram veeraghanta 0587c50ced fix: github setup workflow 2024-05-15 17:42:30 +05:30
guru_sainathandGitHub e1197f2b8f chore: handled multiple children rendering in the space layout (#4459) 2024-05-15 16:28:38 +05:30
Ramesh Kumar ChandraandGitHub 751a4a3b21 [WEB-1311] fix: Issue link copy shortcut macOS (#4455)
* chore: issue link copy shortcut in macos

* chore: dynamic shortcut key render in shortcut pop up

* chore: changing button depending on the os
2024-05-15 15:55:44 +05:30
sriram veeraghanta a2fbd6132b refactor: publish boards 2024-05-15 02:25:38 +05:30
sriram veeraghanta 2b196ba1f1 fix: window workflow build error 2024-05-14 22:51:07 +05:30
sriram veeraghanta 8f6d9b8aca fix: build errors 2024-05-14 22:09:29 +05:30
sriram veeraghanta bcc4524f7f fix: admin auth related fixes 2024-05-14 20:55:07 +05:30
9b7b23f5a2 [WEB-1309] fix: auth fixes (#4456)
* dev: magic link login and email password disable

* dev: user account deactivation

* dev: change nginx conf routes

* feat: changemod space

* fix: space app dir fixes

* dev: invalidate cache for instances when creating workspace

* dev: update email templates for test email

* dev: fix build errors

* fix: auth fixes and improvement (#4452)

* chore: change password api updated and missing password error code added

* chore: auth helper updated

* chore: disable send code input suggestion

* chore: change password function updated

* fix: application error on sign in page

* chore: change password validation added and enhancement

* dev: space base path in web

* dev: admin user deactivated

* dev: user and instance admin session endpoint

* fix: last_workspace_id endpoint updated

* fix: magic sign in and email password check added

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
2024-05-14 20:53:51 +05:30
ab6f1ef780 [WEB-1298] chore: project cycle revamp (#4454)
* chore: cycle endpoint changes

* chore: completed cycle icon updated

* chore: project cycle list revamp and code refactor

* chore: cycle page improvement

* chore: added created by in retrieve endopoint

* fix: build error

* chore: cycle list page disclosure button improvement

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-05-14 19:22:08 +05:30
sriram veeraghantaandGitHub febf19ccc0 feat: converting space app to use nextjs app dir (#4451)
* feat: changemod space

* fix: space app dir fixes

* fix: build errors
2024-05-14 14:26:54 +05:30
sriram veeraghanta 087d54a261 fix: worflow update 2024-05-14 14:26:23 +05:30
Bavisetti NarayanandGitHub c2ce3ada14 chore: update issue link (#4450) 2024-05-14 13:49:14 +05:30
dbc0d7019b [WEB - 1302]dev: nginx headers and auth error codes. (#4449)
* dev: add nginx headers

* fix: handled error messages in admin

---------

Co-authored-by: guru_sainath <gurusainath007@gmail.com>
2024-05-14 13:46:05 +05:30
Manish GuptaandGitHub 2593dc8afc added optional env FORCE_CPU and updated README (#4446) 2024-05-14 13:45:04 +05:30
Satish GandhamandGitHub 18ba4009e0 - Stop the default behavior on the custom menu button. (#4440)
- Refactor menu click handler function
2024-05-13 13:05:10 +05:30
Aaryan KhandelwalandGitHub 198a2a63f2 [WEB-1271] fix: show only joined projects in the filters list (#4417) 2024-05-13 12:06:34 +05:30
sriram veeraghanta 3723ece8d5 fix: postcss upgrade to latest version 2024-05-11 18:55:47 +05:30
sriram veeraghanta 91a66a757a fix: console warnings 2024-05-11 17:47:00 +05:30
Anmol Singh BhatiaandGitHub 4aed6e7aed fix: issue layout application error (#4437) 2024-05-11 16:29:53 +05:30
sriram veeraghanta 16d8dfc86e fix: build errors 2024-05-11 15:14:59 +05:30
Anmol Singh BhatiaandGitHub 3355be9c9c [WEB-1254] chore: list layout indentation enhancement and cycle list page ui improvement (#4435)
* chore: list layout indentation improvement

* chore: cycle list layout spacing and date ui updated

* chore: platform ui improvement
2024-05-11 14:47:56 +05:30
sriram veeraghanta 2ef3c06da0 fix: redirection issues and instance validation changes 2024-05-10 19:34:40 +05:30
M. PalanikannanandGitHub 0ad8bf7664 [WEB-1118] fix: table selections using drag handle fixed (#4429)
* fix: table selections in using drag handle fixed

* fix: not show drag handles for empty p tags
2024-05-10 17:32:23 +05:30
88ebda42ff fix: authentication redirection and UI (#4432)
* dev: update python version

* dev: handle magic code attempt exhausted

* dev: update app, space and god mode redirection paths

* fix: handled signup and signin workflow

* chore: auth input error indication and autofill styling improvement

* dev: add app redirection urls

* dev: update redirections

* chore: onboarding improvement

* chore: onboarding improvement

* chore: redirection issue in space resolved

* chore: instance empty state added

* dev: fix app, space, admin redirection in docker setitngs

---------

Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2024-05-10 17:30:38 +05:30
M. PalanikannanandGitHub 2d1201cc92 fix: button click propagation stopped (#4430) 2024-05-10 16:41:13 +05:30
rahulrameshaandGitHub b725c69882 list and spreadsheet sub issues mutation issue (#4415) 2024-05-10 16:14:15 +05:30
Manish GuptaandGitHub 74eb50aa1a selfhosting fixes for custom branch and platform (#4431) 2024-05-10 16:08:04 +05:30
Anmol Singh BhatiaandGitHub dc77e4afdb chore: project publish modal improvement (#4422) 2024-05-10 15:25:16 +05:30
sriram veeraghanta 27315a8aa2 Merge branch 'preview' of github.com:makeplane/plane into preview 2024-05-10 15:24:32 +05:30
Anmol Singh BhatiaandGitHub da78933c61 [WEB-1274] chore: issue spreadsheet enhancement (#4423)
* chore: border and background remove from cycle and module select

* choe: indentation improvement
2024-05-10 15:24:18 +05:30
sriram veeraghanta 0ab2a98777 Merge branch 'preview' of github.com:makeplane/plane into preview 2024-05-10 15:24:13 +05:30
sriram veeraghanta 40560109b5 fix: admin app redirections 2024-05-10 15:23:51 +05:30
Anmol Singh BhatiaandGitHub 0037377c8f chore: project card enhancement (#4424) 2024-05-10 15:22:01 +05:30
Anmol Singh BhatiaandGitHub 0af55e7bbb [WEB-1250] chore: module list enhancement (#4425)
* chore: move module sub-header to app header

* chore: gantt header improvement, remove title

* chore: progress indicator size reduced

* chore: replace members with lead and updated start and end date ui
2024-05-10 15:21:05 +05:30
Anmol Singh BhatiaandGitHub 57eda34082 chore: notification action item enhancement (#4426) 2024-05-10 15:19:59 +05:30
Anmol Singh BhatiaandGitHub e396424db7 [WEB-1251] chore: view list enhancement (#4427)
* chore: moved search query to mobx store

* chore: moved view sub-header to app header

* chore: created by avatar added in view item list
2024-05-10 15:19:05 +05:30
sriram veeraghanta 243680132e fix: space re-directions 2024-05-10 03:46:45 +05:30
547a76ae55 fix: admin and space redirections (#4419)
* dev: add admin and space base url

* fix: formatting

* dev: add app,space and admin base url to the api env

* fix: updated app base urls redirection

* dev: add change password endpoint

* dev: add none as default for base url

* dev: space password management endpoints

* fix: docker env update

* fix: docker and env settings

* fix: docker changes

* fix: next config update

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
2024-05-10 02:32:42 +05:30
sriram veeraghanta 2320b33189 Merge branch 'preview' of github.com:makeplane/plane into preview 2024-05-09 21:06:08 +05:30
sriram veeraghanta 45bb1153ee fix: removing deploy with nginx env 2024-05-09 21:05:51 +05:30
Anmol Singh BhatiaandGitHub b8768d7274 fix: spreadsheet layout sticky column (#4416)
* fix: spreadsheet layout sticky column

* fix: spreadsheet layout sticky column
2024-05-09 19:06:39 +05:30
58bf056ddb fix: auth redirection issues in the web, space and admin apps (#4414)
* fix: login redirection

* dev: log the user out when deactivating the account

* dev: update redirect uris for google and github

* fix: redirection url and invitation api and add redirection to god mode in nginx

* dev: add reset password redirection

* dev: update nginx headers

* dev: fix setup sh and env example and put validation for use minio when fetching project covers

* dev: stabilize dev setup

* fix: handled redirection error in web, space, and admin apps

* fix: resovled build errors

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-05-09 17:46:31 +05:30
rahulrameshaandGitHub 692f570258 [WEB-1134] fix: module link mutation issue (#4413)
* fix module link mutation issue

* reference moduleDetails?.link_module inside a prop of Disclosure instead of in a div
2024-05-09 16:49:24 +05:30
Anmol Singh BhatiaandGitHub 11cd9f57d7 chore: applied filters update view button alignment improvement (#4407) 2024-05-09 15:52:56 +05:30
Aaryan KhandelwalandGitHub 3a66986785 [WEB-1256] fix: accept image as a valid comment (#4409)
* fix: accept image as a valid comment

* fix: update space app helper function
2024-05-09 15:52:17 +05:30
rahulrameshaandGitHub d8ab3e0087 [WEB-1139] chore: Calendar pragmatic dnd (#4410)
* replace Pragmatic DND for calendar

* remove unnecessary check
2024-05-09 15:51:49 +05:30
439 changed files with 6716 additions and 4800 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
extends: ["custom"],
settings: {
next: {
rootDir: ["web/", "space/"],
rootDir: ["web/", "space/", "admin/"],
},
},
};
+16 -16
View File
@@ -14,10 +14,10 @@ jobs:
space_changed: ${{ steps.changed-files.outputs.space_any_changed }}
web_changed: ${{ steps.changed-files.outputs.web_any_changed }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
uses: tj-actions/changed-files@v44
with:
files_yaml: |
apiserver:
@@ -49,9 +49,9 @@ jobs:
runs-on: ubuntu-latest
if: needs.get-changed-files.outputs.apiserver_changed == 'true'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.x" # Specify the Python version you need
- name: Install Pylint
@@ -66,9 +66,9 @@ jobs:
if: needs.get-changed-files.outputs.admin_changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: yarn install
@@ -79,9 +79,9 @@ jobs:
if: needs.get-changed-files.outputs.space_changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: yarn install
@@ -92,9 +92,9 @@ jobs:
if: needs.get-changed-files.outputs.web_changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: yarn install
@@ -104,9 +104,9 @@ jobs:
needs: lint-admin
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: yarn install
@@ -116,9 +116,9 @@ jobs:
needs: lint-space
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: yarn install
@@ -128,9 +128,9 @@ jobs:
needs: lint-web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: yarn install
+17 -11
View File
@@ -5,17 +5,17 @@ on:
inputs:
web-build:
required: false
description: 'Build Web'
description: "Build Web"
type: boolean
default: true
space-build:
required: false
description: 'Build Space'
description: "Build Space"
type: boolean
default: false
admin-build:
required: false
description: 'Build Admin'
description: "Build Admin"
type: boolean
default: false
@@ -35,7 +35,7 @@ jobs:
echo "BUILD_SPACE=$BUILD_SPACE"
echo "BUILD_ADMIN=$BUILD_ADMIN"
outputs:
web-build: ${{ env.BUILD_WEB}}
web-build: ${{ env.BUILD_WEB}}
space-build: ${{env.BUILD_SPACE}}
admin-build: ${{env.BUILD_ADMIN}}
@@ -53,7 +53,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: "18"
- name: Install AWS cli
run: |
sudo apt-get update
@@ -79,7 +79,7 @@ jobs:
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
feature-build-space:
if: ${{ needs.setup-feature-build.outputs.space-build == 'true' }}
needs: setup-feature-build
@@ -89,7 +89,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ vars.FEATURE_PREVIEW_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.FEATURE_PREVIEW_AWS_SECRET_ACCESS_KEY }}
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 }}
outputs:
do-build: ${{ needs.setup-feature-build.outputs.space-build }}
@@ -98,7 +98,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: "18"
- name: Install AWS cli
run: |
sudo apt-get update
@@ -134,7 +134,7 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ vars.FEATURE_PREVIEW_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.FEATURE_PREVIEW_AWS_SECRET_ACCESS_KEY }}
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 }}
outputs:
do-build: ${{ needs.setup-feature-build.outputs.admin-build }}
@@ -143,7 +143,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: "18"
- name: Install AWS cli
run: |
sudo apt-get update
@@ -172,7 +172,13 @@ jobs:
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') }}
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
runs-on: ubuntu-latest
env:
+3 -2
View File
@@ -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=""
+39 -1
View File
@@ -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,
},
},
],
},
}
+30 -7
View File
@@ -1,3 +1,6 @@
# *****************************************************************************
# STAGE 1: Build the project
# *****************************************************************************
FROM node:18-alpine AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app
@@ -7,6 +10,9 @@ COPY . .
RUN turbo prune --scope=admin --docker
# *****************************************************************************
# STAGE 2: Install dependencies & build the project
# *****************************************************************************
FROM node:18-alpine AS installer
RUN apk add --no-cache libc6-compat
@@ -21,13 +27,25 @@ COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
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_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
# *****************************************************************************
# STAGE 3: Copy the project and start it
# *****************************************************************************
FROM node:18-alpine AS runner
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/public ./admin/public
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
+17
View File
@@ -0,0 +1,17 @@
FROM node:18-alpine
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
COPY . .
RUN yarn global add turbo
RUN yarn install
ENV NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
EXPOSE 3000
VOLUME [ "/app/node_modules", "/app/admin/node_modules" ]
CMD ["yarn", "dev", "--filter=admin"]
-1
View File
@@ -1 +0,0 @@
export * from "./ai-config-form";
@@ -1,12 +1,12 @@
import { FC } from "react";
import { useForm } from "react-hook-form";
import { Lightbulb } from "lucide-react";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ControllerInput, TControllerInputFormField } from "components/common";
import { ControllerInput, TControllerInputFormField } from "@/components/common";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
type IInstanceAIForm = {
config: IFormattedInstanceConfiguration;
+3 -15
View File
@@ -2,20 +2,8 @@
import { ReactNode } from "react";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
import { AdminLayout } from "@/layouts/admin-layout";
interface AILayoutProps {
children: ReactNode;
export default function AILayout({ children }: { children: ReactNode }) {
return <AdminLayout>{children}</AdminLayout>;
}
const AILayout = ({ children }: AILayoutProps) => (
<InstanceWrapper>
<AuthWrapper>
<AdminLayout>{children}</AdminLayout>
</AuthWrapper>
</InstanceWrapper>
);
export default AILayout;
+4 -3
View File
@@ -1,13 +1,14 @@
"use client";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Loader } from "@plane/ui";
// components
import { PageHeader } from "@/components/core";
import { InstanceAIForm } from "./components";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
// components
import { InstanceAIForm } from "./form";
const InstanceAIPage = observer(() => {
// store
@@ -1 +0,0 @@
export * from "./authentication-method-card";
@@ -3,11 +3,11 @@
import React from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useInstance } from "@/hooks";
// ui
import { ToggleSwitch } from "@plane/ui";
// types
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { useInstance } from "@/hooks/store";
// ui
// types
type Props = {
disabled: boolean;
@@ -1,18 +1,18 @@
"use client";
import React from "react";
import Link from "next/link";
import { observer } from "mobx-react-lite";
// hooks
import { useInstance } from "@/hooks";
// ui
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// types
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
// ui
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
// helpers
import { cn } from "helpers/common.helper";
import { cn } from "@/helpers/common.helper";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
disabled: boolean;
@@ -1,18 +1,18 @@
"use client";
import React from "react";
import Link from "next/link";
import { observer } from "mobx-react-lite";
// hooks
import { useInstance } from "@/hooks";
// ui
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// types
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
// ui
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
// helpers
import { cn } from "helpers/common.helper";
import { cn } from "@/helpers/common.helper";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
disabled: boolean;
+3 -1
View File
@@ -1,3 +1,5 @@
export * from "./common";
export * from "./email-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 { observer } from "mobx-react-lite";
// hooks
import { useInstance } from "@/hooks";
// ui
import { ToggleSwitch } from "@plane/ui";
// types
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { useInstance } from "@/hooks/store";
// ui
// types
type Props = {
disabled: boolean;
@@ -1,2 +0,0 @@
export * from "./root";
export * from "./github-config-form";
@@ -1,8 +1,9 @@
import { FC, useState } from "react";
import { useForm } from "react-hook-form";
import isEmpty from "lodash/isEmpty";
import Link from "next/link";
// hooks
import { useInstance } from "@/hooks";
import { useForm } from "react-hook-form";
// types
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
// ui
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
// components
@@ -12,12 +13,11 @@ import {
CopyField,
TControllerInputFormField,
TCopyField,
} from "components/common";
// types
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
} from "@/components/common";
// helpers
import { API_BASE_URL, cn } from "helpers/common.helper";
import isEmpty from "lodash/isEmpty";
import { API_BASE_URL, cn } from "@/helpers/common.helper";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
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 githubFormFields: TControllerInputFormField[] = [
const GITHUB_FORM_FIELDS: TControllerInputFormField[] = [
{
key: "GITHUB_CLIENT_ID",
type: "text",
@@ -55,6 +55,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
<>
You will get this from your{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
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{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
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",
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{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
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{" "}
<a
tabIndex={-1}
href="https://github.com/settings/applications/new"
target="_blank"
className="text-custom-primary-100 hover:underline"
@@ -134,13 +138,16 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
const payload: Partial<GithubConfigFormValues> = { ...formData };
await updateInstanceConfigurations(payload)
.then(() => {
.then((response = []) => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
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));
};
@@ -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="flex flex-col gap-y-4 col-span-2 md:col-span-1">
<div className="pt-2 text-xl font-medium">Configuration</div>
{githubFormFields.map((field) => (
{GITHUB_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
control={control}
@@ -194,7 +201,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
<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="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} />
))}
</div>
+6 -5
View File
@@ -1,22 +1,23 @@
"use client";
import { useState } from "react";
import { observer } from "mobx-react-lite";
import Image from "next/image";
import { useTheme } from "next-themes";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
// components
import { PageHeader } from "@/components/core";
import { AuthenticationMethodCard } from "../components";
import { InstanceGithubConfigForm } from "./components";
// hooks
import { useInstance } from "@/hooks";
// helpers
import { resolveGeneralTheme } from "@/helpers/common.helper";
// hooks
import { useInstance } from "@/hooks/store";
// icons
import githubLightModeImage from "@/public/logos/github-black.png";
import githubDarkModeImage from "@/public/logos/github-white.png";
// local components
import { AuthenticationMethodCard } from "../components";
import { InstanceGithubConfigForm } from "./form";
const InstanceGithubAuthenticationPage = observer(() => {
// store
@@ -1,2 +0,0 @@
export * from "./root";
export * from "./google-config-form";
@@ -1,8 +1,9 @@
import { FC, useState } from "react";
import { useForm } from "react-hook-form";
import isEmpty from "lodash/isEmpty";
import Link from "next/link";
// hooks
import { useInstance } from "@/hooks";
import { useForm } from "react-hook-form";
// types
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
// ui
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
// components
@@ -12,12 +13,11 @@ import {
CopyField,
TControllerInputFormField,
TCopyField,
} from "components/common";
// types
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
} from "@/components/common";
// helpers
import { API_BASE_URL, cn } from "helpers/common.helper";
import isEmpty from "lodash/isEmpty";
import { API_BASE_URL, cn } from "@/helpers/common.helper";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
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 googleFormFields: TControllerInputFormField[] = [
const GOOGLE_FORM_FIELDS: TControllerInputFormField[] = [
{
key: "GOOGLE_CLIENT_ID",
type: "text",
@@ -55,6 +55,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
<>
Your client ID lives in your Google API Console.{" "}
<a
tabIndex={-1}
href="https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#creatingcred"
target="_blank"
className="text-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.{" "}
<a
tabIndex={-1}
href="https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid"
target="_blank"
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",
label: "Origin URL",
@@ -134,13 +136,16 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
const payload: Partial<GoogleConfigFormValues> = { ...formData };
await updateInstanceConfigurations(payload)
.then(() => {
.then((response = []) => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success",
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));
};
@@ -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="flex flex-col gap-y-4 col-span-2 md:col-span-1">
<div className="pt-2 text-xl font-medium">Configuration</div>
{googleFormFields.map((field) => (
{GOOGLE_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
control={control}
@@ -194,7 +199,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
<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="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} />
))}
</div>
+5 -4
View File
@@ -1,18 +1,19 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { observer } from "mobx-react-lite";
import Image from "next/image";
import useSWR from "swr";
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
// components
import { PageHeader } from "@/components/core";
import { AuthenticationMethodCard } from "../components";
import { InstanceGoogleConfigForm } from "./components";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
// icons
import GoogleLogo from "@/public/logos/google-logo.svg";
// local components
import { AuthenticationMethodCard } from "../components";
import { InstanceGoogleConfigForm } from "./form";
const InstanceGoogleAuthenticationPage = observer(() => {
// store
+3 -15
View File
@@ -2,20 +2,8 @@
import { ReactNode } from "react";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
import { AdminLayout } from "@/layouts/admin-layout";
interface AuthenticationLayoutProps {
children: ReactNode;
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
return <AdminLayout>{children}</AdminLayout>;
}
const AuthenticationLayout = ({ children }: AuthenticationLayoutProps) => (
<InstanceWrapper>
<AuthWrapper>
<AdminLayout>{children}</AdminLayout>
</AuthWrapper>
</InstanceWrapper>
);
export default AuthenticationLayout;
+12 -7
View File
@@ -1,26 +1,31 @@
"use client";
import { useState } from "react";
import { observer } from "mobx-react-lite";
import Image from "next/image";
import { useTheme } from "next-themes";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import { Mails, KeyRound } from "lucide-react";
import { Loader, setPromiseToast } from "@plane/ui";
import { TInstanceConfigurationKeys } from "@plane/types";
import { Loader, setPromiseToast } from "@plane/ui";
// components
import { AuthenticationMethodCard, EmailCodesConfiguration, PasswordLoginConfiguration } from "./components";
import { GoogleConfiguration } from "./google/components";
import { GithubConfiguration } from "./github/components";
import { PageHeader } from "@/components/core";
// hooks
import { useInstance } from "@/hooks";
// helpers
import { resolveGeneralTheme } from "@/helpers/common.helper";
import { useInstance } from "@/hooks/store";
// images
import GoogleLogo from "@/public/logos/google-logo.svg";
import githubLightModeImage from "@/public/logos/github-black.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 = {
key: string;
-2
View File
@@ -1,2 +0,0 @@
export * from "./email-config-form";
export * from "./test-email-modal";
@@ -1,14 +1,15 @@
import React, { FC, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
// hooks
import { useInstance } from "@/hooks";
// types
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
// ui
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ControllerInput, TControllerInputFormField } from "components/common";
import { ControllerInput, TControllerInputFormField } from "@/components/common";
// hooks
import { useInstance } from "@/hooks/store";
// local components
import { SendTestEmailModal } from "./test-email-modal";
// types
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
type IInstanceEmailForm = {
config: IFormattedInstanceConfiguration;
+2 -10
View File
@@ -2,20 +2,12 @@
import { ReactNode } from "react";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
import { AdminLayout } from "@/layouts/admin-layout";
interface EmailLayoutProps {
children: ReactNode;
}
const EmailLayout = ({ children }: EmailLayoutProps) => (
<InstanceWrapper>
<AuthWrapper>
<AdminLayout>{children}</AdminLayout>
</AuthWrapper>
</InstanceWrapper>
);
const EmailLayout = ({ children }: EmailLayoutProps) => <AdminLayout>{children}</AdminLayout>;
export default EmailLayout;
+4 -3
View File
@@ -1,13 +1,14 @@
"use client";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Loader } from "@plane/ui";
// components
import { PageHeader } from "@/components/core";
import { InstanceEmailForm } from "./components";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
// components
import { InstanceEmailForm } from "./email-config-form";
const InstanceEmailPage = observer(() => {
// store
@@ -3,7 +3,7 @@ import { Dialog, Transition } from "@headlessui/react";
// ui
import { Button, Input } from "@plane/ui";
// services
import { InstanceService } from "services/instance.service";
import { InstanceService } from "@/services/instance.service";
type Props = {
isOpen: boolean;
+9
View File
@@ -0,0 +1,9 @@
"use client";
export default function RootErrorPage() {
return (
<div>
<p>Something went wrong.</p>
</div>
);
}
-1
View File
@@ -1 +0,0 @@
export * from "./general-config-form";
@@ -1,19 +1,23 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
import { Telescope } from "lucide-react";
// types
import { IInstance, IInstanceAdmin } from "@plane/types";
// ui
import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
// components
import { ControllerInput } from "components/common";
import { ControllerInput } from "@/components/common";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
export interface IGeneralConfigurationForm {
instance: IInstance["instance"];
instanceAdmins: IInstanceAdmin[];
}
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) => {
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer((props) => {
const { instance, instanceAdmins } = props;
// hooks
const { updateInstanceInfo } = useInstance();
@@ -24,8 +28,8 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) =
formState: { errors, isSubmitting },
} = useForm<Partial<IInstance["instance"]>>({
defaultValues: {
instance_name: instance.instance_name,
is_telemetry_enabled: instance.is_telemetry_enabled,
instance_name: instance?.instance_name,
is_telemetry_enabled: instance?.is_telemetry_enabled,
},
});
@@ -133,4 +137,4 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) =
</div>
</div>
);
};
});
+9 -18
View File
@@ -1,21 +1,12 @@
"use client";
import { ReactNode } from "react";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
import { Metadata } from "next";
// components
import { AdminLayout } from "@/layouts/admin-layout";
interface GeneralLayoutProps {
children: ReactNode;
export const metadata: Metadata = {
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;
+9 -12
View File
@@ -1,18 +1,15 @@
"use client";
import { observer } from "mobx-react-lite";
// components
import { PageHeader } from "@/components/core";
import { GeneralConfigurationForm } from "./components";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
// components
import { GeneralConfigurationForm } from "./form";
const GeneralPage = observer(() => {
function GeneralPage() {
const { instance, instanceAdmins } = useInstance();
console.log("instance", instanceAdmins);
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="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>
@@ -22,13 +19,13 @@ const GeneralPage = observer(() => {
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
{instance?.instance && instanceAdmins && instanceAdmins?.length > 0 && (
<GeneralConfigurationForm instance={instance?.instance} instanceAdmins={instanceAdmins} />
{instance?.instance && instanceAdmins && (
<GeneralConfigurationForm instance={instance.instance} instanceAdmins={instanceAdmins} />
)}
</div>
</div>
</>
);
});
}
export default GeneralPage;
export default observer(GeneralPage);
-1
View File
@@ -1 +0,0 @@
export * from "./image-config-form";
@@ -1,11 +1,11 @@
import { FC } from "react";
import { useForm } from "react-hook-form";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// components
import { ControllerInput } from "components/common";
import { ControllerInput } from "@/components/common";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
type IInstanceImageConfigForm = {
config: IFormattedInstanceConfiguration;
+2 -12
View File
@@ -1,21 +1,11 @@
"use client";
import { ReactNode } from "react";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
import { AdminLayout } from "@/layouts/admin-layout";
interface ImageLayoutProps {
children: ReactNode;
}
const ImageLayout = ({ children }: ImageLayoutProps) => (
<InstanceWrapper>
<AuthWrapper>
<AdminLayout>{children}</AdminLayout>
</AuthWrapper>
</InstanceWrapper>
);
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
export default ImageLayout;
+4 -3
View File
@@ -1,13 +1,14 @@
"use client";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Loader } from "@plane/ui";
// components
import { PageHeader } from "@/components/core";
import { InstanceImageConfigForm } from "./components";
// hooks
import { useInstance } from "@/hooks";
import { useInstance } from "@/hooks/store";
// local
import { InstanceImageConfigForm } from "./form";
const InstanceImagePage = observer(() => {
// store
+57 -33
View File
@@ -1,48 +1,72 @@
"use client";
import { ReactNode } from "react";
import { ThemeProvider } from "next-themes";
import { Metadata } from "next";
// components
import { InstanceFailureView, InstanceSetupForm } from "@/components/instance";
// helpers
import { ASSET_PREFIX } from "@/helpers/common.helper";
// layout
import { DefaultLayout } from "@/layouts/default-layout";
// lib
import { StoreProvider } from "@/lib/store-context";
import { AppWrapper } from "@/lib/wrappers";
// constants
import { SITE_NAME, SITE_DESCRIPTION, SITE_URL, TWITTER_USER_NAME, SITE_KEYWORDS, SITE_TITLE } from "@/constants/seo";
import { AppProvider } from "@/lib/app-providers";
// styles
import "./globals.css";
// services
import { InstanceService } from "@/services/instance.service";
interface RootLayoutProps {
children: ReactNode;
}
const instanceService = new InstanceService();
const RootLayout = ({ children, ...pageProps }: RootLayoutProps) => {
const prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/god-mode/";
export const metadata: Metadata = {
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 (
<html lang="en">
<head>
<title>{SITE_TITLE}</title>
<meta property="og:site_name" content={SITE_NAME} />
<meta property="og:title" content={SITE_TITLE} />
<meta property="og:url" content={SITE_URL} />
<meta name="description" content={SITE_DESCRIPTION} />
<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`} />
<link rel="apple-touch-icon" sizes="180x180" href={`${ASSET_PREFIX}/favicon/apple-touch-icon.png`} />
<link rel="icon" type="image/png" sizes="32x32" href={`${ASSET_PREFIX}/favicon/favicon-32x32.png`} />
<link rel="icon" type="image/png" sizes="16x16" href={`${ASSET_PREFIX}/favicon/favicon-16x16.png`} />
<link rel="manifest" href={`${ASSET_PREFIX}/site.webmanifest.json`} />
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
</head>
<body className={`antialiased`}>
<StoreProvider {...pageProps}>
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<AppWrapper>{children}</AppWrapper>
</ThemeProvider>
</StoreProvider>
<AppProvider initialState={{ instance: instanceDetails }}>
{instanceDetails ? (
<>
{instanceDetails?.instance?.is_setup_done ? (
<>{children}</>
) : (
<DefaultLayout>
<div className="relative w-screen min-h-screen overflow-y-auto px-5 py-10 mx-auto flex justify-center items-center">
<InstanceSetupForm />
</div>
</DefaultLayout>
)}
</>
) : (
<DefaultLayout>
<div className="relative w-screen min-h-[500px] overflow-y-auto px-5 mx-auto flex justify-center items-center">
<InstanceFailureView />
</div>
</DefaultLayout>
)}
</AppProvider>
</body>
</html>
);
};
export default RootLayout;
}
-19
View File
@@ -1,19 +0,0 @@
"use client";
import { ReactNode } from "react";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
// helpers
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
interface LoginLayoutProps {
children: ReactNode;
}
const LoginLayout = ({ children }: LoginLayoutProps) => (
<InstanceWrapper pageType={EInstancePageType.POST_SETUP}>
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>{children}</AuthWrapper>
</InstanceWrapper>
);
export default LoginLayout;
-18
View File
@@ -1,18 +0,0 @@
"use client";
// layouts
import { DefaultLayout } from "@/layouts";
// components
import { PageHeader } from "@/components/core";
import { InstanceSignInForm } from "./components";
const LoginPage = () => (
<>
<PageHeader title="Setup - God Mode" />
<DefaultLayout>
<InstanceSignInForm />
</DefaultLayout>
</>
);
export default LoginPage;
+8 -17
View File
@@ -1,20 +1,11 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
// components
import { PageHeader } from "@/components/core";
const RootPage = () => {
const router = useRouter();
useEffect(() => router.push("/login"), [router]);
import { InstanceSignInForm } from "@/components/login";
// layouts
import { DefaultLayout } from "@/layouts/default-layout";
export default async function LoginPage() {
return (
<>
<PageHeader title="Plane - God Mode" />
</>
<DefaultLayout>
<InstanceSignInForm />
</DefaultLayout>
);
};
export default RootPage;
}
-1
View File
@@ -1 +0,0 @@
export * from "./sign-up-form";
-19
View File
@@ -1,19 +0,0 @@
"use client";
import { ReactNode } from "react";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
// helpers
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
interface SetupLayoutProps {
children: ReactNode;
}
const SetupLayout = ({ children }: SetupLayoutProps) => (
<InstanceWrapper pageType={EInstancePageType.PRE_SETUP}>
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>{children}</AuthWrapper>
</InstanceWrapper>
);
export default SetupLayout;
-16
View File
@@ -1,16 +0,0 @@
// layouts
import { DefaultLayout } from "@/layouts";
// components
import { PageHeader } from "@/components/core";
import { InstanceSignUpForm } from "./components";
const SetupPage = () => (
<>
<PageHeader title="Setup - God Mode" />
<DefaultLayout>
<InstanceSignUpForm />
</DefaultLayout>
</>
);
export default SetupPage;
@@ -1,12 +1,14 @@
"use client";
import { FC, useState, useRef } from "react";
import { Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
import Link from "next/link";
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
import { Transition } from "@headlessui/react";
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
// hooks
import { useTheme } from "@/hooks";
import { WEB_BASE_URL } from "@/helpers/common.helper";
import { useTheme } from "@/hooks/store";
// assets
import packageJson from "package.json";
@@ -28,7 +30,7 @@ const helpOptions = [
},
];
export const HelpSection: FC = () => {
export const HelpSection: FC = observer(() => {
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// store
@@ -36,7 +38,7 @@ export const HelpSection: FC = () => {
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
const redirectionLink = `${process.env.NEXT_PUBLIC_APP_URL ? `${process.env.NEXT_PUBLIC_APP_URL}/create-workspace` : `${process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX === "1" ? `/god-mode/` : `/`}`}`;
const redirectionLink = encodeURI(WEB_BASE_URL + "/create-workspace");
return (
<div
@@ -128,4 +130,4 @@ export const HelpSection: FC = () => {
</div>
</div>
);
};
});
+2 -2
View File
@@ -3,10 +3,10 @@
import { FC, useEffect, useRef } from "react";
import { observer } from "mobx-react-lite";
// 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";
// components
import { HelpSection, SidebarMenu, SidebarDropdown } from "@/components/admin-sidebar";
export interface IInstanceSidebar {}
@@ -1,17 +1,17 @@
"use client";
import { Fragment, useEffect, useState } from "react";
import { useTheme as useNextTheme } from "next-themes";
import { observer } from "mobx-react-lite";
import { useTheme as useNextTheme } from "next-themes";
import { LogOut, UserCog2, Palette } from "lucide-react";
import { Menu, Transition } from "@headlessui/react";
import { Avatar } from "@plane/ui";
// hooks
import { useTheme, useUser } from "@/hooks";
// helpers
import { API_BASE_URL, cn } from "@/helpers/common.helper";
import { useTheme, useUser } from "@/hooks/store";
// helpers
// services
import { AuthService } from "@/services";
import { AuthService } from "@/services/auth.service";
// service initialization
const authService = new AuthService();
@@ -3,9 +3,9 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useTheme } from "@/hooks";
// icons
import { Menu } from "lucide-react";
import { useTheme } from "@/hooks/store";
// icons
export const SidebarHamburgerToggle: FC = observer(() => {
const { isSidebarCollapsed, toggleSidebar } = useTheme();
@@ -1,14 +1,14 @@
"use client";
import { observer } from "mobx-react-lite";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { observer } from "mobx-react-lite";
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
import { Tooltip } from "@plane/ui";
// hooks
import { useTheme } from "@/hooks";
// helpers
import { cn } from "@/helpers/common.helper";
import { useTheme } from "@/hooks/store";
// helpers
const INSTANCE_ADMIN_LINKS = [
{
+2 -2
View File
@@ -1,16 +1,16 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { usePathname } from "next/navigation";
// mobx
import { observer } from "mobx-react-lite";
// ui
import { Settings } from "lucide-react";
// icons
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "components/common";
import { SidebarHamburgerToggle } from "@/components/admin-sidebar";
import { BreadcrumbLink } from "components/common";
export const InstanceHeader: FC = observer(() => {
const pathName = usePathname();
+3 -1
View File
@@ -3,9 +3,9 @@
import React, { useState } from "react";
import { Controller, Control } from "react-hook-form";
// ui
import { Eye, EyeOff } from "lucide-react";
import { Input } from "@plane/ui";
// icons
import { Eye, EyeOff } from "lucide-react";
// helpers
import { cn } from "@/helpers/common.helper";
@@ -62,6 +62,7 @@ export const ControllerInput: React.FC<Props> = (props) => {
{type === "password" &&
(showPassword ? (
<button
tabIndex={-1}
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(false)}
>
@@ -69,6 +70,7 @@ export const ControllerInput: React.FC<Props> = (props) => {
</button>
) : (
<button
tabIndex={-1}
className="absolute right-3 top-2.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(true)}
>
+2 -2
View File
@@ -2,9 +2,9 @@
import React from "react";
// ui
import { Copy } from "lucide-react";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Copy } from "lucide-react";
type Props = {
label: string;
@@ -40,7 +40,7 @@ export const CopyField: React.FC<Props> = (props) => {
<p className="text-sm font-medium">{url}</p>
<Copy size={18} color="#B9B9B9" />
</Button>
<p className="text-xs text-custom-text-400">{description}</p>
<div className="text-xs text-custom-text-400">{description}</div>
</div>
);
};
+46
View 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>
);
+2
View File
@@ -4,3 +4,5 @@ export * from "./controller-input";
export * from "./copy-field";
export * from "./password-strength-meter";
export * from "./banner";
export * from "./empty-state";
export * from "./logo-spinner";
+17
View File
@@ -0,0 +1,17 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// assets
import LogoSpinnerDark from "@/public/images/logo-spinner-dark.gif";
import LogoSpinnerLight from "@/public/images/logo-spinner-light.gif";
export const LogoSpinner = () => {
const { resolvedTheme } = useTheme();
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerDark : LogoSpinnerLight;
return (
<div className="flex items-center justify-center">
<Image src={logoSrc} alt="logo" className="w-[82px] h-[82px] mr-2" />
</div>
);
};
@@ -1,10 +1,10 @@
"use client";
// helpers
import { CircleCheck } from "lucide-react";
import { cn } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper";
// icons
import { CircleCheck } from "lucide-react";
type Props = {
password: string;
+2
View File
@@ -1 +1,3 @@
export * from "./instance-not-ready";
export * from "./instance-failure-view";
export * from "./setup-form";
@@ -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";
import { FC } from "react";
import Link from "next/link";
import Image from "next/image";
import Link from "next/link";
import { Button } from "@plane/ui";
// assets
import PlaneTakeOffImage from "@/public/images/plane-takeoff.png";
@@ -2,17 +2,17 @@
import { FC, useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
// services
import { AuthService } from "@/services/auth.service";
// icons
import { Eye, EyeOff } from "lucide-react";
// ui
import { Button, Checkbox, Input, Spinner } from "@plane/ui";
// components
import { Banner, PasswordStrengthMeter } from "components/common";
// icons
import { Eye, EyeOff } from "lucide-react";
import { Banner, PasswordStrengthMeter } from "@/components/common";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper";
// services
import { AuthService } from "@/services/auth.service";
// service initialization
const authService = new AuthService();
@@ -52,7 +52,7 @@ const defaultFromData: TFormData = {
is_telemetry_enabled: true,
};
export const InstanceSignUpForm: FC = (props) => {
export const InstanceSetupForm: FC = (props) => {
const {} = props;
// search params
const searchParams = useSearchParams();
@@ -69,6 +69,7 @@ export const InstanceSignUpForm: FC = (props) => {
const [formData, setFormData] = useState<TFormData>(defaultFromData);
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false);
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
setFormData((prev) => ({ ...prev, [key]: value }));
@@ -122,7 +123,7 @@ export const InstanceSignUpForm: FC = (props) => {
);
return (
<div className="relative w-full h-full overflow-hidden container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 flex flex-col justify-center items-center">
<div className="max-w-lg px-10 lg:max-w-md lg:px-5">
<div className="relative flex flex-col space-y-6">
<div className="text-center space-y-1">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
@@ -154,7 +155,7 @@ export const InstanceSignUpForm: FC = (props) => {
First name <span className="text-red-500">*</span>
</label>
<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"
name="first_name"
type="text"
@@ -167,10 +168,10 @@ export const InstanceSignUpForm: FC = (props) => {
</div>
<div className="w-full space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="last_name">
Last name
Last name <span className="text-red-500">*</span>
</label>
<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"
name="last_name"
type="text"
@@ -187,7 +188,7 @@ export const InstanceSignUpForm: FC = (props) => {
Email <span className="text-red-500">*</span>
</label>
<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"
name="email"
type="email"
@@ -204,10 +205,10 @@ export const InstanceSignUpForm: FC = (props) => {
<div className="w-full space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="company_name">
Company name
Company name <span className="text-red-500">*</span>
</label>
<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"
name="company_name"
type="text"
@@ -224,7 +225,7 @@ export const InstanceSignUpForm: FC = (props) => {
</label>
<div className="relative">
<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"
name="password"
type={showPassword ? "text" : "password"}
@@ -239,6 +240,7 @@ export const InstanceSignUpForm: FC = (props) => {
{showPassword ? (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(false)}
>
@@ -247,6 +249,7 @@ export const InstanceSignUpForm: FC = (props) => {
) : (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(true)}
>
@@ -262,7 +265,7 @@ export const InstanceSignUpForm: FC = (props) => {
<div className="w-full space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
Confirm password
Confirm password <span className="text-red-500">*</span>
</label>
<div className="relative">
<Input
@@ -274,10 +277,13 @@ export const InstanceSignUpForm: FC = (props) => {
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password"
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)}
/>
{showPassword ? (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(false)}
>
@@ -286,6 +292,7 @@ export const InstanceSignUpForm: FC = (props) => {
) : (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(true)}
>
@@ -293,9 +300,9 @@ export const InstanceSignUpForm: FC = (props) => {
</button>
)}
</div>
{!!formData.confirm_password && formData.password !== formData.confirm_password && (
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
)}
{!!formData.confirm_password &&
formData.password !== formData.confirm_password &&
!isRetryPasswordInputFocused && <span className="text-sm text-red-500">Passwords don{"'"}t match</span>}
</div>
<div className="relative flex items-center pt-2 gap-2">
@@ -314,7 +321,13 @@ export const InstanceSignUpForm: FC = (props) => {
>
Allow Plane to anonymously collect usage events.
</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
</a>
</div>
@@ -3,15 +3,15 @@
import { FC, useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
// services
import { AuthService } from "@/services/auth.service";
// ui
import { Eye, EyeOff } from "lucide-react";
import { Button, Input, Spinner } from "@plane/ui";
// components
import { Banner } from "components/common";
// icons
import { Eye, EyeOff } from "lucide-react";
import { Banner } from "@/components/common";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
import { AuthService } from "@/services/auth.service";
// ui
// icons
// service initialization
const authService = new AuthService();
@@ -57,6 +57,8 @@ export const InstanceSignInForm: FC = (props) => {
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
setFormData((prev) => ({ ...prev, [key]: value }));
console.log("csrfToken", csrfToken);
useEffect(() => {
if (csrfToken === undefined)
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>
</label>
<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"
name="email"
type="email"
@@ -137,7 +139,7 @@ export const InstanceSignInForm: FC = (props) => {
</label>
<div className="relative">
<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"
name="password"
type={showPassword ? "text" : "password"}
+4 -2
View File
@@ -8,18 +8,20 @@ import { useTheme as nextUseTheme } from "next-themes";
import { Button, getButtonStyling } from "@plane/ui";
// helpers
import { resolveGeneralTheme } from "helpers/common.helper";
// hooks
import { useInstance, useTheme } from "@/hooks/store";
// icons
import TakeoffIconLight from "/public/logos/takeoff-icon-light.svg";
import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
import { useTheme } from "@/hooks";
export const NewUserPopup: React.FC = observer(() => {
// hooks
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
const { instance } = useInstance();
// theme
const { resolvedTheme } = nextUseTheme();
const redirectionLink = `${process.env.NEXT_PUBLIC_APP_URL ? `${process.env.NEXT_PUBLIC_APP_URL}/create-workspace` : `${process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX === "1" ? `/god-mode/` : `/`}`}`;
const redirectionLink = `${instance?.config?.app_base_url ? `${instance?.config?.app_base_url}/create-workspace` : `/god-mode/`}`;
if (!isNewUserPopup) return <></>;
return (
+54 -53
View File
@@ -1,10 +1,12 @@
import { ReactNode } from "react";
import Link from "next/link";
export enum EPageTypes {
"PUBLIC" = "PUBLIC",
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
"ONBOARDING" = "ONBOARDING",
"AUTHENTICATED" = "AUTHENTICATED",
PUBLIC = "PUBLIC",
NON_AUTHENTICATED = "NON_AUTHENTICATED",
SET_PASSWORD = "SET_PASSWORD",
ONBOARDING = "ONBOARDING",
AUTHENTICATED = "AUTHENTICATED",
}
export enum EAuthModes {
@@ -18,28 +20,26 @@ export enum EAuthSteps {
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 {
BANNER_ALERT = "BANNER_ALERT",
TOAST_ALERT = "TOAST_ALERT",
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
INLINE_EMAIL = "INLINE_EMAIL",
INLINE_PASSWORD = "INLINE_PASSWORD",
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 = {
type: EErrorAlertType;
code: EAuthenticationErrorCodes;
@@ -50,41 +50,54 @@ export type TAuthErrorInfo = {
const errorCodeMessages: {
[key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
} = {
[EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: {
title: "Instance not configured",
message: () => "Please contact your administrator to configure the instance.",
},
// admin
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
title: "Admin already exists",
message: () => "Admin already exists. Please sign in.",
title: `Admin already exists`,
message: () => `Admin already exists. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
title: "Required",
message: () => "Please enter email, password and first name.",
title: `Email, password and first name required`,
message: () => `Email, password and first name required. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
title: "Invalid email",
message: () => "Please enter a valid email.",
title: `Invalid admin email`,
message: () => `Invalid admin email. Please try again.`,
},
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
title: "Invalid password",
message: () => "Password must be at least 8 characters long.",
title: `Invalid admin password`,
message: () => `Invalid admin password. Please try again.`,
},
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
title: "Required",
message: () => "Please enter email and password.",
title: `Email and password required`,
message: () => `Email and password required. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
title: "Authentication failed",
message: () => "Please check your email and password and try again.",
title: `Authentication failed`,
message: () => `Authentication failed. Please try again.`,
},
[EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
title: "User already exists",
message: () => "User already exists. Please sign in.",
title: `Admin user already exists`,
message: () => (
<div>
Admin user already exists.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
Sign In
</Link>
&nbsp;now.
</div>
),
},
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
title: "User does not exist",
message: () => "User does not exist. Please sign up.",
title: `Admin user does not exist`,
message: () => (
<div>
Admin user does not exist.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
Sign In
</Link>
&nbsp;now.
</div>
),
},
};
@@ -92,28 +105,16 @@ export const authErrorHandler = (
errorCode: EAuthenticationErrorCodes,
email?: string | undefined
): TAuthErrorInfo | undefined => {
const toastAlertErrorCodes = [
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
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.ADMIN_AUTHENTICATION_FAILED,
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_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))
return {
type: EErrorAlertType.BANNER_ALERT,
+10 -1
View File
@@ -1,7 +1,16 @@
import { clsx, type ClassValue } from "clsx";
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));
-6
View File
@@ -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
View File
@@ -0,0 +1,3 @@
export * from "./use-theme";
export * from "./use-instance";
export * from "./use-user";
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-context";
import { StoreContext } from "@/lib/app-providers";
import { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-context";
import { StoreContext } from "@/lib/app-providers";
import { IThemeStore } from "@/store/theme.store";
export const useTheme = (): IThemeStore => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-context";
import { StoreContext } from "@/lib/app-providers";
import { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {
+35 -3
View File
@@ -1,15 +1,47 @@
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";
// components
import { InstanceSidebar } from "@/components/admin-sidebar";
import { InstanceHeader } from "@/components/auth-header";
import { LogoSpinner } from "@/components/common";
import { NewUserPopup } from "@/components/new-user-popup";
// hooks
import { useInstance, useUser } from "@/hooks/store";
type TAdminLayout = {
children: ReactNode;
};
export const AdminLayout: FC<TAdminLayout> = (props) => {
export const AdminLayout: FC<TAdminLayout> = observer((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">
<LogoSpinner />
</div>
);
}
return (
<div className="relative flex h-screen w-screen overflow-hidden">
@@ -21,4 +53,4 @@ export const AdminLayout: FC<TAdminLayout> = (props) => {
<NewUserPopup />
</div>
);
};
});
+2 -5
View File
@@ -17,6 +17,7 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
const { children, withoutBackground = false } = props;
// hooks
const { resolvedTheme } = useTheme();
const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern;
return (
<div className="relative">
@@ -29,11 +30,7 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
</div>
{!withoutBackground && (
<div className="absolute inset-0 z-0">
<Image
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
className="w-screen h-full object-cover"
alt="Plane background pattern"
/>
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
</div>
)}
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
-2
View File
@@ -1,2 +0,0 @@
export * from "./default-layout";
export * from "./admin-layout";
+43
View File
@@ -0,0 +1,43 @@
"use client";
import { ReactNode, createContext } from "react";
import { ThemeProvider } from "next-themes";
// store
import { RootStore } from "@/store/root.store";
// store initialization
import { AppWrapper } from "./app-wrapper";
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) {
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>
);
};
@@ -3,14 +3,14 @@
import { FC, ReactNode, useEffect, Suspense } from "react";
import { observer } from "mobx-react-lite";
import { SWRConfig } from "swr";
// hooks
import { useTheme, useUser } from "@/hooks";
// ui
import { Toast } from "@plane/ui";
// constants
import { SWR_CONFIG } from "constants/swr-config";
import { SWR_CONFIG } from "@/constants/swr-config";
// helpers
import { resolveGeneralTheme } from "helpers/common.helper";
import { resolveGeneralTheme } from "@/helpers/common.helper";
// hooks
import { useTheme, useUser } from "@/hooks/store";
interface IAppWrapper {
children: ReactNode;
-21
View File
@@ -1,21 +0,0 @@
"use client";
import { ReactElement, createContext } from "react";
// mobx store
import { RootStore } from "@/store/root-store";
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>;
};
-59
View File
@@ -1,59 +0,0 @@
"use client";
import { FC, ReactNode } from "react";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Spinner } from "@plane/ui";
// hooks
import { useInstance, useUser } from "@/hooks";
// helpers
import { EAuthenticationPageType, EUserStatus } from "@/helpers";
import { redirect } from "next/navigation";
export interface IAuthWrapper {
children: ReactNode;
authType?: EAuthenticationPageType;
}
export const AuthWrapper: FC<IAuthWrapper> = observer((props) => {
const { children, authType = EAuthenticationPageType.AUTHENTICATED } = props;
// hooks
const { instance, fetchInstanceAdmins } = useInstance();
const { isLoading, userStatus, currentUser, fetchCurrentUser } = useUser();
useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
shouldRetryOnError: false,
});
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins(), {
shouldRetryOnError: false,
});
if (isLoading)
return (
<div className="relative flex h-screen w-full items-center justify-center">
<Spinner />
</div>
);
if (userStatus && userStatus?.status === EUserStatus.ERROR)
return (
<div className="relative flex h-screen w-screen items-center justify-center">
Something went wrong. please try again later
</div>
);
if ([EAuthenticationPageType.AUTHENTICATED, EAuthenticationPageType.NOT_AUTHENTICATED].includes(authType)) {
if (authType === EAuthenticationPageType.NOT_AUTHENTICATED) {
if (currentUser === undefined) return <>{children}</>;
else redirect("/general/");
} else {
if (currentUser) return <>{children}</>;
else {
if (instance?.instance?.is_setup_done) redirect("/login/");
else redirect("/setup/");
}
}
}
return <>{children}</>;
});
-3
View File
@@ -1,3 +0,0 @@
export * from "./app-wrapper";
export * from "./instance-wrapper";
export * from "./auth-wrapper";
-59
View File
@@ -1,59 +0,0 @@
"use client";
import { FC, ReactNode } from "react";
import { redirect, useSearchParams } from "next/navigation";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Spinner } from "@plane/ui";
// layouts
import { DefaultLayout } from "@/layouts";
// components
import { InstanceNotReady } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks";
// helpers
import { EInstancePageType, EInstanceStatus } from "@/helpers";
type TInstanceWrapper = {
children: ReactNode;
pageType?: EInstancePageType;
};
export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
const { children, pageType } = props;
const searchparams = useSearchParams();
const authEnabled = searchparams.get("auth_enabled") || "1";
// hooks
const { isLoading, instanceStatus, instance, fetchInstanceInfo } = useInstance();
useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
revalidateOnFocus: false,
});
if (isLoading)
return (
<div className="relative flex h-screen w-full items-center justify-center">
<Spinner />
</div>
);
if (instanceStatus && instanceStatus?.status === EInstanceStatus.ERROR)
return (
<div className="relative flex h-screen w-screen items-center justify-center">
Something went wrong. please try again later
</div>
);
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
return (
<DefaultLayout withoutBackground>
<InstanceNotReady />
</DefaultLayout>
);
if (instance?.instance?.is_setup_done && pageType === EInstancePageType.PRE_SETUP) redirect("/");
if (!instance?.instance?.is_setup_done && pageType === EInstancePageType.POST_SETUP) redirect("/setup");
return <>{children}</>;
});
+1 -1
View File
@@ -7,7 +7,7 @@ const nextConfig = {
images: {
unoptimized: true,
},
basePath: "/god-mode",
basePath: process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || "",
};
module.exports = nextConfig;
+3 -2
View File
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "turbo run develop",
"develop": "next dev --port 3333",
"develop": "next dev --port 3001",
"build": "next build",
"preview": "next build && next start",
"start": "next start",
@@ -14,6 +14,7 @@
"@headlessui/react": "^1.7.19",
"@plane/types": "*",
"@plane/ui": "*",
"@plane/constants": "*",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash": "^4.17.0",
"autoprefixer": "10.4.14",
@@ -25,7 +26,7 @@
"mobx-react-lite": "^4.0.5",
"next": "^14.2.3",
"next-themes": "^0.2.1",
"postcss": "8.4.23",
"postcss": "^8.4.38",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -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

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+10 -7
View File
@@ -1,4 +1,6 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
// store
// import { rootStore } from "@/lib/store-context";
export abstract class APIService {
protected baseURL: string;
@@ -15,13 +17,14 @@ export abstract class APIService {
}
private setupInterceptors() {
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) window.location.href = "/login";
return Promise.reject(error);
}
);
// this.axiosInstance.interceptors.response.use(
// (response) => response,
// (error) => {
// const store = rootStore;
// if (error.response && error.response.status === 401 && store.user.currentUser) store.user.reset();
// return Promise.reject(error);
// }
// );
}
get<ResponseType>(url: string, params = {}): Promise<AxiosResponse<ResponseType>> {
+2 -2
View File
@@ -1,7 +1,7 @@
// services
import { APIService } from "services/api.service";
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
type TCsrfTokenResponse = {
csrf_token: string;
-3
View File
@@ -1,3 +0,0 @@
export * from "./auth.service";
export * from "./instance.service";
export * from "./user.service";
+3 -3
View File
@@ -1,8 +1,8 @@
import { APIService } from "services/api.service";
// types
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
// 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 {
constructor() {
@@ -13,7 +13,7 @@ export class InstanceService extends APIService {
return this.get<IInstance>("/api/instances/")
.then((response) => response.data)
.catch((error) => {
throw error;
throw error?.response?.data;
});
}
+12 -2
View File
@@ -1,15 +1,25 @@
// helpers
import { API_BASE_URL } from "helpers/common.helper";
// services
import { APIService } from "services/api.service";
// 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 {
constructor() {
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> {
return this.get<IUser>("/api/instances/admins/me/")
.then((response) => response?.data)
+16 -8
View File
@@ -1,12 +1,12 @@
import { observable, action, computed, makeObservable, runInAction } from "mobx";
import set from "lodash/set";
import { observable, action, computed, makeObservable, runInAction } from "mobx";
import { IInstance, IInstanceAdmin, IInstanceConfiguration, IFormattedInstanceConfiguration } from "@plane/types";
// helpers
import { EInstanceStatus, TInstanceStatus } from "@/helpers";
// services
import { InstanceService } from "@/services/instance.service";
// root store
import { RootStore } from "@/store/root-store";
import { RootStore } from "@/store/root.store";
export interface IInstanceStore {
// issues
@@ -18,11 +18,12 @@ export interface IInstanceStore {
// computed
formattedConfig: IFormattedInstanceConfiguration | undefined;
// action
hydrate: (data: any) => void;
fetchInstanceInfo: () => Promise<IInstance | undefined>;
updateInstanceInfo: (data: Partial<IInstance["instance"]>) => Promise<IInstance["instance"] | undefined>;
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<void>;
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<IInstanceConfiguration[]>;
}
export class InstanceStore implements IInstanceStore {
@@ -45,6 +46,7 @@ export class InstanceStore implements IInstanceStore {
// computed
formattedConfig: computed,
// actions
hydrate: action,
fetchInstanceInfo: action,
fetchInstanceAdmins: action,
updateInstanceInfo: action,
@@ -55,6 +57,10 @@ export class InstanceStore implements IInstanceStore {
this.instanceService = new InstanceService();
}
hydrate = (data: any) => {
if (data) this.instance = data;
};
/**
* computed value for instance configurations data for forms.
* @returns configurations in the form of {key, value} pair.
@@ -148,13 +154,15 @@ export class InstanceStore implements IInstanceStore {
*/
updateInstanceConfigurations = async (data: Partial<IFormattedInstanceConfiguration>) => {
try {
await this.instanceService.updateInstanceConfigurations(data).then((response) => {
runInAction(() => {
this.instanceConfigurations = this.instanceConfigurations
? [...this.instanceConfigurations, ...response]
: response;
const response = await this.instanceService.updateInstanceConfigurations(data);
runInAction(() => {
this.instanceConfigurations = this.instanceConfigurations?.map((config) => {
const item = response.find((item) => item.key === config.key);
if (item) return item;
return config;
});
});
return response;
} catch (error) {
console.error("Error updating the instance configurations");
throw error;
@@ -1,7 +1,7 @@
import { enableStaticRendering } from "mobx-react-lite";
// stores
import { IThemeStore, ThemeStore } from "./theme.store";
import { IInstanceStore, InstanceStore } from "./instance.store";
import { IThemeStore, ThemeStore } from "./theme.store";
import { IUserStore, UserStore } from "./user.store";
enableStaticRendering(typeof window === "undefined");
@@ -17,9 +17,16 @@ export class RootStore {
this.user = new UserStore(this);
}
hydrate(initialData: any) {
this.theme.hydrate(initialData.theme);
this.instance.hydrate(initialData.instance);
this.user.hydrate(initialData.user);
}
resetOnSignOut() {
this.theme = new ThemeStore(this);
localStorage.setItem("theme", "system");
this.instance = new InstanceStore(this);
this.user = new UserStore(this);
this.theme = new ThemeStore(this);
}
}
+6 -1
View File
@@ -1,6 +1,6 @@
import { action, observable, makeObservable } from "mobx";
// root store
import { RootStore } from "@/store/root-store";
import { RootStore } from "@/store/root.store";
type TTheme = "dark" | "light";
export interface IThemeStore {
@@ -9,6 +9,7 @@ export interface IThemeStore {
theme: string | undefined;
isSidebarCollapsed: boolean | undefined;
// actions
hydrate: (data: any) => void;
toggleNewUserPopup: () => void;
toggleSidebar: (collapsed: boolean) => 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
*/
+33 -12
View File
@@ -3,10 +3,10 @@ import { IUser } from "@plane/types";
// helpers
import { EUserStatus, TUserStatus } from "@/helpers";
// services
import { AuthService } from "@/services/auth.service";
import { UserService } from "@/services/user.service";
// root store
import { RootStore } from "@/store/root-store";
import { AuthService } from "@/services";
import { RootStore } from "@/store/root.store";
export interface IUserStore {
// observables
@@ -15,8 +15,10 @@ export interface IUserStore {
isUserLoggedIn: boolean | undefined;
currentUser: IUser | undefined;
// fetch actions
hydrate: (data: any) => void;
fetchCurrentUser: () => Promise<IUser>;
signOut: () => Promise<void>;
reset: () => void;
signOut: () => void;
}
export class UserStore implements IUserStore {
@@ -28,8 +30,6 @@ export class UserStore implements IUserStore {
// services
userService;
authService;
// rootStore
rootStore;
constructor(private store: RootStore) {
makeObservable(this, {
@@ -40,12 +40,17 @@ export class UserStore implements IUserStore {
currentUser: observable,
// action
fetchCurrentUser: action,
reset: action,
signOut: action,
});
this.userService = new UserService();
this.authService = new AuthService();
this.rootStore = store;
}
hydrate = (data: any) => {
if (data) this.currentUser = data;
};
/**
* @description Fetches the current user
* @returns Promise<IUser>
@@ -54,11 +59,20 @@ export class UserStore implements IUserStore {
try {
if (this.currentUser === undefined) this.isLoading = true;
const currentUser = await this.userService.currentUser();
runInAction(() => {
this.isUserLoggedIn = true;
this.currentUser = currentUser;
this.isLoading = false;
});
if (currentUser) {
await this.store.instance.fetchInstanceAdmins();
runInAction(() => {
this.isUserLoggedIn = true;
this.currentUser = currentUser;
this.isLoading = false;
});
} else {
runInAction(() => {
this.isUserLoggedIn = false;
this.currentUser = undefined;
this.isLoading = false;
});
}
return currentUser;
} catch (error: any) {
this.isLoading = false;
@@ -77,7 +91,14 @@ export class UserStore implements IUserStore {
}
};
reset = async () => {
this.isUserLoggedIn = false;
this.currentUser = undefined;
this.isLoading = false;
this.userStatus = undefined;
};
signOut = async () => {
this.rootStore.resetOnSignOut();
this.store.resetOnSignOut();
};
}
+6 -1
View File
@@ -1,7 +1,7 @@
# Backend
# Debug value for api server use it as 0 for production use
DEBUG=0
CORS_ALLOWED_ORIGINS=""
CORS_ALLOWED_ORIGINS="http://localhost"
# Error logs
SENTRY_DSN=""
@@ -44,3 +44,8 @@ WEB_URL="http://localhost"
# Gunicorn Workers
GUNICORN_WORKERS=2
# Base URLs
ADMIN_BASE_URL=
SPACE_BASE_URL=
APP_BASE_URL=

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