Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5e1619c98 |
@@ -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@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v44
|
||||
uses: tj-actions/changed-files@v41
|
||||
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@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
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@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v2
|
||||
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@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v2
|
||||
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@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@@ -104,9 +104,9 @@ jobs:
|
||||
needs: lint-admin
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@@ -116,9 +116,9 @@ jobs:
|
||||
needs: lint-space
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
@@ -128,9 +128,9 @@ jobs:
|
||||
needs: lint-web
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
- run: yarn install
|
||||
|
||||
+1
-39
@@ -10,43 +10,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
rules: {}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Lightbulb } from "lucide-react";
|
||||
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { ControllerInput, TControllerInputFormField } from "@/components/common";
|
||||
import { ControllerInput, TControllerInputFormField } from "components/common";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ai-config-form";
|
||||
+15
-3
@@ -2,8 +2,20 @@
|
||||
|
||||
import { ReactNode } from "react";
|
||||
// layouts
|
||||
import { AdminLayout } from "@/layouts/admin-layout";
|
||||
import { AdminLayout } from "@/layouts";
|
||||
// lib
|
||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||
|
||||
export default function AILayout({ children }: { children: ReactNode }) {
|
||||
return <AdminLayout>{children}</AdminLayout>;
|
||||
interface AILayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const AILayout = ({ children }: AILayoutProps) => (
|
||||
<InstanceWrapper>
|
||||
<AuthWrapper>
|
||||
<AdminLayout>{children}</AdminLayout>
|
||||
</AuthWrapper>
|
||||
</InstanceWrapper>
|
||||
);
|
||||
|
||||
export default AILayout;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { PageHeader } from "@/components/core";
|
||||
import { InstanceAIForm } from "./components";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import { InstanceAIForm } from "./form";
|
||||
|
||||
const InstanceAIPage = observer(() => {
|
||||
// store
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./authentication-method-card";
|
||||
@@ -3,11 +3,11 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
// types
|
||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
disabled: boolean;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
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 { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
// types
|
||||
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
disabled: boolean;
|
||||
|
||||
+14
-21
@@ -1,9 +1,8 @@
|
||||
import { FC, useState } from "react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import Link from "next/link";
|
||||
import { useForm } from "react-hook-form";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
|
||||
import Link from "next/link";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||
// components
|
||||
@@ -13,11 +12,12 @@ import {
|
||||
CopyField,
|
||||
TControllerInputFormField,
|
||||
TCopyField,
|
||||
} from "@/components/common";
|
||||
} from "components/common";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
|
||||
// helpers
|
||||
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
import { API_BASE_URL, cn } from "helpers/common.helper";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
|
||||
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 GITHUB_FORM_FIELDS: TControllerInputFormField[] = [
|
||||
const githubFormFields: TControllerInputFormField[] = [
|
||||
{
|
||||
key: "GITHUB_CLIENT_ID",
|
||||
type: "text",
|
||||
@@ -55,7 +55,6 @@ 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"
|
||||
@@ -77,7 +76,6 @@ 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"
|
||||
@@ -93,7 +91,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
||||
},
|
||||
];
|
||||
|
||||
const GITHUB_SERVICE_FIELD: TCopyField[] = [
|
||||
const githubCopyFields: TCopyField[] = [
|
||||
{
|
||||
key: "Origin_URL",
|
||||
label: "Origin URL",
|
||||
@@ -102,7 +100,6 @@ 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"
|
||||
@@ -121,7 +118,6 @@ 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"
|
||||
@@ -138,16 +134,13 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
|
||||
const payload: Partial<GithubConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then((response = []) => {
|
||||
.then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success",
|
||||
message: "Github Configuration Settings updated successfully",
|
||||
});
|
||||
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,
|
||||
});
|
||||
reset();
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
};
|
||||
@@ -170,7 +163,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>
|
||||
{GITHUB_FORM_FIELDS.map((field) => (
|
||||
{githubFormFields.map((field) => (
|
||||
<ControllerInput
|
||||
key={field.key}
|
||||
control={control}
|
||||
@@ -201,7 +194,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>
|
||||
{GITHUB_SERVICE_FIELD.map((field) => (
|
||||
{githubCopyFields.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
))}
|
||||
</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./root";
|
||||
export * from "./github-config-form";
|
||||
+6
-6
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
|
||||
// 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";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
import { cn } from "helpers/common.helper";
|
||||
|
||||
type Props = {
|
||||
disabled: boolean;
|
||||
@@ -1,23 +1,22 @@
|
||||
"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";
|
||||
// helpers
|
||||
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
||||
import { AuthenticationMethodCard } from "../components";
|
||||
import { InstanceGithubConfigForm } from "./components";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// helpers
|
||||
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
||||
// 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
|
||||
|
||||
+14
-19
@@ -1,9 +1,8 @@
|
||||
import { FC, useState } from "react";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import Link from "next/link";
|
||||
import { useForm } from "react-hook-form";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
|
||||
import Link from "next/link";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||
// components
|
||||
@@ -13,11 +12,12 @@ import {
|
||||
CopyField,
|
||||
TControllerInputFormField,
|
||||
TCopyField,
|
||||
} from "@/components/common";
|
||||
} from "components/common";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
|
||||
// helpers
|
||||
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
import { API_BASE_URL, cn } from "helpers/common.helper";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
|
||||
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 GOOGLE_FORM_FIELDS: TControllerInputFormField[] = [
|
||||
const googleFormFields: TControllerInputFormField[] = [
|
||||
{
|
||||
key: "GOOGLE_CLIENT_ID",
|
||||
type: "text",
|
||||
@@ -55,7 +55,6 @@ 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"
|
||||
@@ -77,7 +76,6 @@ 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"
|
||||
@@ -93,7 +91,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
||||
},
|
||||
];
|
||||
|
||||
const GOOGLE_SERVICE_DETAILS: TCopyField[] = [
|
||||
const googleCopyFeilds: TCopyField[] = [
|
||||
{
|
||||
key: "Origin_URL",
|
||||
label: "Origin URL",
|
||||
@@ -136,16 +134,13 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
|
||||
const payload: Partial<GoogleConfigFormValues> = { ...formData };
|
||||
|
||||
await updateInstanceConfigurations(payload)
|
||||
.then((response = []) => {
|
||||
.then(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success",
|
||||
message: "Google Configuration Settings updated successfully",
|
||||
});
|
||||
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,
|
||||
});
|
||||
reset();
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
};
|
||||
@@ -168,7 +163,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>
|
||||
{GOOGLE_FORM_FIELDS.map((field) => (
|
||||
{googleFormFields.map((field) => (
|
||||
<ControllerInput
|
||||
key={field.key}
|
||||
control={control}
|
||||
@@ -199,7 +194,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>
|
||||
{GOOGLE_SERVICE_DETAILS.map((field) => (
|
||||
{googleCopyFeilds.map((field) => (
|
||||
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
|
||||
))}
|
||||
</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./root";
|
||||
export * from "./google-config-form";
|
||||
+6
-6
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
|
||||
// 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";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
import { cn } from "helpers/common.helper";
|
||||
|
||||
type Props = {
|
||||
disabled: boolean;
|
||||
@@ -1,19 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Image from "next/image";
|
||||
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 { InstanceGoogleConfigForm } from "./components";
|
||||
// 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
|
||||
|
||||
@@ -2,8 +2,20 @@
|
||||
|
||||
import { ReactNode } from "react";
|
||||
// layouts
|
||||
import { AdminLayout } from "@/layouts/admin-layout";
|
||||
import { AdminLayout } from "@/layouts";
|
||||
// lib
|
||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||
|
||||
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
|
||||
return <AdminLayout>{children}</AdminLayout>;
|
||||
interface AuthenticationLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const AuthenticationLayout = ({ children }: AuthenticationLayoutProps) => (
|
||||
<InstanceWrapper>
|
||||
<AuthWrapper>
|
||||
<AdminLayout>{children}</AdminLayout>
|
||||
</AuthWrapper>
|
||||
</InstanceWrapper>
|
||||
);
|
||||
|
||||
export default AuthenticationLayout;
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
"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 { TInstanceConfigurationKeys } from "@plane/types";
|
||||
import { Loader, setPromiseToast } from "@plane/ui";
|
||||
import { TInstanceConfigurationKeys } from "@plane/types";
|
||||
// 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/store";
|
||||
// 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;
|
||||
|
||||
+5
-6
@@ -1,15 +1,14 @@
|
||||
import React, { FC, useMemo, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// ui
|
||||
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ControllerInput, TControllerInputFormField } from "@/components/common";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// local components
|
||||
import { ControllerInput, TControllerInputFormField } from "components/common";
|
||||
import { SendTestEmailModal } from "./test-email-modal";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
|
||||
|
||||
type IInstanceEmailForm = {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./email-config-form";
|
||||
export * from "./test-email-modal";
|
||||
+1
-1
@@ -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;
|
||||
@@ -2,12 +2,20 @@
|
||||
|
||||
import { ReactNode } from "react";
|
||||
// layouts
|
||||
import { AdminLayout } from "@/layouts/admin-layout";
|
||||
import { AdminLayout } from "@/layouts";
|
||||
// lib
|
||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||
|
||||
interface EmailLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const EmailLayout = ({ children }: EmailLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
||||
const EmailLayout = ({ children }: EmailLayoutProps) => (
|
||||
<InstanceWrapper>
|
||||
<AuthWrapper>
|
||||
<AdminLayout>{children}</AdminLayout>
|
||||
</AuthWrapper>
|
||||
</InstanceWrapper>
|
||||
);
|
||||
|
||||
export default EmailLayout;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { PageHeader } from "@/components/core";
|
||||
import { InstanceEmailForm } from "./components";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import { InstanceEmailForm } from "./email-config-form";
|
||||
|
||||
const InstanceEmailPage = observer(() => {
|
||||
// store
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export default function RootErrorPage() {
|
||||
return (
|
||||
<div>
|
||||
<p>Something went wrong.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
"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/store";
|
||||
|
||||
@@ -17,7 +13,7 @@ export interface IGeneralConfigurationForm {
|
||||
instanceAdmins: IInstanceAdmin[];
|
||||
}
|
||||
|
||||
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer((props) => {
|
||||
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) => {
|
||||
const { instance, instanceAdmins } = props;
|
||||
// hooks
|
||||
const { updateInstanceInfo } = useInstance();
|
||||
@@ -28,8 +24,8 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -137,4 +133,4 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./general-config-form";
|
||||
@@ -1,12 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { Metadata } from "next";
|
||||
// components
|
||||
import { AdminLayout } from "@/layouts/admin-layout";
|
||||
// layouts
|
||||
import { AdminLayout } from "@/layouts";
|
||||
// lib
|
||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "General Settings - God Mode",
|
||||
};
|
||||
|
||||
export default function GeneralLayout({ children }: { children: ReactNode }) {
|
||||
return <AdminLayout>{children}</AdminLayout>;
|
||||
interface GeneralLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const GeneralLayout = ({ children }: GeneralLayoutProps) => (
|
||||
<InstanceWrapper>
|
||||
<AuthWrapper>
|
||||
<AdminLayout>{children}</AdminLayout>
|
||||
</AuthWrapper>
|
||||
</InstanceWrapper>
|
||||
);
|
||||
|
||||
export default GeneralLayout;
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { PageHeader } from "@/components/core";
|
||||
import { GeneralConfigurationForm } from "./components";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// components
|
||||
import { GeneralConfigurationForm } from "./form";
|
||||
|
||||
function GeneralPage() {
|
||||
const GeneralPage = observer(() => {
|
||||
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>
|
||||
@@ -19,13 +22,13 @@ function GeneralPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow overflow-hidden overflow-y-auto">
|
||||
{instance?.instance && instanceAdmins && (
|
||||
<GeneralConfigurationForm instance={instance.instance} instanceAdmins={instanceAdmins} />
|
||||
{instance?.instance && instanceAdmins && instanceAdmins?.length > 0 && (
|
||||
<GeneralConfigurationForm instance={instance?.instance} instanceAdmins={instanceAdmins} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default observer(GeneralPage);
|
||||
export default GeneralPage;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FC } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
||||
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
|
||||
// components
|
||||
import { ControllerInput } from "@/components/common";
|
||||
import { ControllerInput } from "components/common";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./image-config-form";
|
||||
@@ -1,11 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
// layouts
|
||||
import { AdminLayout } from "@/layouts/admin-layout";
|
||||
import { AdminLayout } from "@/layouts";
|
||||
// lib
|
||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||
|
||||
interface ImageLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
|
||||
const ImageLayout = ({ children }: ImageLayoutProps) => (
|
||||
<InstanceWrapper>
|
||||
<AuthWrapper>
|
||||
<AdminLayout>{children}</AdminLayout>
|
||||
</AuthWrapper>
|
||||
</InstanceWrapper>
|
||||
);
|
||||
|
||||
export default ImageLayout;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { PageHeader } from "@/components/core";
|
||||
import { InstanceImageConfigForm } from "./components";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store";
|
||||
// local
|
||||
import { InstanceImageConfigForm } from "./form";
|
||||
|
||||
const InstanceImagePage = observer(() => {
|
||||
// store
|
||||
|
||||
+39
-65
@@ -1,72 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { Metadata } from "next";
|
||||
// components
|
||||
import { InstanceFailureView, InstanceSetupForm } from "@/components/instance";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
// 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";
|
||||
// helpers
|
||||
import { ASSET_PREFIX } from "@/helpers/common.helper";
|
||||
// layout
|
||||
import { DefaultLayout } from "@/layouts/default-layout";
|
||||
// lib
|
||||
import { AppProvider } from "@/lib/app-providers";
|
||||
// styles
|
||||
import "./globals.css";
|
||||
// services
|
||||
import { InstanceService } from "@/services/instance.service";
|
||||
|
||||
const instanceService = new InstanceService();
|
||||
|
||||
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>
|
||||
<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`}>
|
||||
<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>
|
||||
);
|
||||
interface RootLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const RootLayout = ({ children, ...pageProps }: RootLayoutProps) => (
|
||||
<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={`${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>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
export default RootLayout;
|
||||
|
||||
+25
-10
@@ -1,11 +1,26 @@
|
||||
import { InstanceSignInForm } from "@/components/login";
|
||||
// layouts
|
||||
import { DefaultLayout } from "@/layouts/default-layout";
|
||||
"use client";
|
||||
|
||||
export default async function LoginPage() {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<InstanceSignInForm />
|
||||
</DefaultLayout>
|
||||
);
|
||||
}
|
||||
// layouts
|
||||
import { DefaultLayout } from "@/layouts";
|
||||
// components
|
||||
import { PageHeader } from "@/components/core";
|
||||
import { InstanceSignInForm } from "@/components/login";
|
||||
// lib
|
||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||
// helpers
|
||||
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
|
||||
|
||||
const LoginPage = () => (
|
||||
<>
|
||||
<PageHeader title="Login - God Mode" />
|
||||
<InstanceWrapper pageType={EInstancePageType.POST_SETUP}>
|
||||
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>
|
||||
<DefaultLayout>
|
||||
<InstanceSignInForm />
|
||||
</DefaultLayout>
|
||||
</AuthWrapper>
|
||||
</InstanceWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./sign-up-form";
|
||||
+19
-32
@@ -2,17 +2,17 @@
|
||||
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
// ui
|
||||
import { Button, Checkbox, Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
import { Banner, PasswordStrengthMeter } from "@/components/common";
|
||||
import { Banner, PasswordStrengthMeter } from "components/common";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// 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 InstanceSetupForm: FC = (props) => {
|
||||
export const InstanceSignUpForm: FC = (props) => {
|
||||
const {} = props;
|
||||
// search params
|
||||
const searchParams = useSearchParams();
|
||||
@@ -69,7 +69,6 @@ export const InstanceSetupForm: 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 }));
|
||||
@@ -123,7 +122,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="max-w-lg px-10 lg:max-w-md lg:px-5">
|
||||
<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="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">
|
||||
@@ -155,7 +154,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
First name <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
@@ -168,10 +167,10 @@ export const InstanceSetupForm: 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 <span className="text-red-500">*</span>
|
||||
Last name
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
@@ -188,7 +187,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
Email <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
@@ -205,10 +204,10 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="company_name">
|
||||
Company name <span className="text-red-500">*</span>
|
||||
Company name
|
||||
</label>
|
||||
<Input
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="company_name"
|
||||
name="company_name"
|
||||
type="text"
|
||||
@@ -225,7 +224,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
@@ -240,7 +239,6 @@ export const InstanceSetupForm: 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)}
|
||||
>
|
||||
@@ -249,7 +247,6 @@ export const InstanceSetupForm: 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)}
|
||||
>
|
||||
@@ -265,7 +262,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
|
||||
<div className="w-full space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
Confirm password <span className="text-red-500">*</span>
|
||||
Confirm password
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
@@ -277,13 +274,10 @@ export const InstanceSetupForm: 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)}
|
||||
>
|
||||
@@ -292,7 +286,6 @@ export const InstanceSetupForm: 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)}
|
||||
>
|
||||
@@ -300,9 +293,9 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{!!formData.confirm_password &&
|
||||
formData.password !== formData.confirm_password &&
|
||||
!isRetryPasswordInputFocused && <span className="text-sm text-red-500">Passwords don{"'"}t match</span>}
|
||||
{!!formData.confirm_password && formData.password !== formData.confirm_password && (
|
||||
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="relative flex items-center pt-2 gap-2">
|
||||
@@ -321,13 +314,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||
>
|
||||
Allow Plane to anonymously collect usage events.
|
||||
</label>
|
||||
<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"
|
||||
>
|
||||
<a href="https://docs.plane.so/telemetry" className="text-sm font-medium text-blue-500 hover:text-blue-600">
|
||||
See More
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
"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;
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useState, useRef } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
|
||||
import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui";
|
||||
// hooks
|
||||
import { WEB_BASE_URL } from "@/helpers/common.helper";
|
||||
import { useTheme } from "@/hooks/store";
|
||||
import { useInstance, useTheme } from "@/hooks/store";
|
||||
// assets
|
||||
import packageJson from "package.json";
|
||||
|
||||
@@ -31,6 +30,8 @@ const helpOptions = [
|
||||
];
|
||||
|
||||
export const HelpSection: FC = observer(() => {
|
||||
// hooks
|
||||
const { instance } = useInstance();
|
||||
// states
|
||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||
// store
|
||||
@@ -38,7 +39,7 @@ export const HelpSection: FC = observer(() => {
|
||||
// refs
|
||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const redirectionLink = encodeURI(WEB_BASE_URL + "/create-workspace");
|
||||
const redirectionLink = `${instance?.config?.app_base_url ? `${instance?.config?.app_base_url}/create-workspace` : `/god-mode/`}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import { FC, useEffect, useRef } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// 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 { observer } from "mobx-react-lite";
|
||||
import { useTheme as useNextTheme } from "next-themes";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { LogOut, UserCog2, Palette } from "lucide-react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Avatar } from "@plane/ui";
|
||||
// hooks
|
||||
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||
import { useTheme, useUser } from "@/hooks/store";
|
||||
// helpers
|
||||
import { API_BASE_URL, cn } from "@/helpers/common.helper";
|
||||
// services
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
import { AuthService } from "@/services";
|
||||
|
||||
// service initialization
|
||||
const authService = new AuthService();
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { Menu } from "lucide-react";
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// icons
|
||||
import { Menu } from "lucide-react";
|
||||
|
||||
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 { cn } from "@/helpers/common.helper";
|
||||
import { useTheme } from "@/hooks/store";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
const INSTANCE_ADMIN_LINKS = [
|
||||
{
|
||||
|
||||
@@ -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 { SidebarHamburgerToggle } from "@/components/admin-sidebar";
|
||||
import { BreadcrumbLink } from "components/common";
|
||||
import { SidebarHamburgerToggle } from "@/components/admin-sidebar";
|
||||
|
||||
export const InstanceHeader: FC = observer(() => {
|
||||
const pathName = usePathname();
|
||||
|
||||
@@ -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,7 +62,6 @@ 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)}
|
||||
>
|
||||
@@ -70,7 +69,6 @@ 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,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>
|
||||
<div className="text-xs text-custom-text-400">{description}</div>
|
||||
<p className="text-xs text-custom-text-400">{description}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,4 +5,3 @@ export * from "./copy-field";
|
||||
export * from "./password-strength-meter";
|
||||
export * from "./banner";
|
||||
export * from "./empty-state";
|
||||
export * from "./logo-spinner";
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
export * from "./instance-not-ready";
|
||||
export * from "./instance-failure-view";
|
||||
export * from "./setup-form";
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
"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 Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import PlaneTakeOffImage from "@/public/images/plane-takeoff.png";
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
// services
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
import { Banner } from "@/components/common";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
import { AuthService } from "@/services/auth.service";
|
||||
// ui
|
||||
import { Button, Input, Spinner } from "@plane/ui";
|
||||
// components
|
||||
import { Banner } from "components/common";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
|
||||
// service initialization
|
||||
const authService = new AuthService();
|
||||
@@ -57,8 +57,6 @@ 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));
|
||||
@@ -121,7 +119,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 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
@@ -139,7 +137,7 @@ export const InstanceSignInForm: FC = (props) => {
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
|
||||
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||
id="password"
|
||||
name="password"
|
||||
type={showPassword ? "text" : "password"}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export enum EPageTypes {
|
||||
PUBLIC = "PUBLIC",
|
||||
NON_AUTHENTICATED = "NON_AUTHENTICATED",
|
||||
SET_PASSWORD = "SET_PASSWORD",
|
||||
ONBOARDING = "ONBOARDING",
|
||||
AUTHENTICATED = "AUTHENTICATED",
|
||||
"PUBLIC" = "PUBLIC",
|
||||
"NON_AUTHENTICATED" = "NON_AUTHENTICATED",
|
||||
"ONBOARDING" = "ONBOARDING",
|
||||
"AUTHENTICATED" = "AUTHENTICATED",
|
||||
}
|
||||
|
||||
export enum EAuthModes {
|
||||
@@ -20,26 +18,28 @@ 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,54 +50,41 @@ export type TAuthErrorInfo = {
|
||||
const errorCodeMessages: {
|
||||
[key in EAuthenticationErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
|
||||
} = {
|
||||
// admin
|
||||
[EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED]: {
|
||||
title: "Instance not configured",
|
||||
message: () => "Please contact your administrator to configure the instance.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
|
||||
title: `Admin already exists`,
|
||||
message: () => `Admin already exists. Please try again.`,
|
||||
title: "Admin already exists",
|
||||
message: () => "Admin already exists. Please sign in.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
||||
title: `Email, password and first name required`,
|
||||
message: () => `Email, password and first name required. Please try again.`,
|
||||
title: "Required",
|
||||
message: () => "Please enter email, password and first name.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
|
||||
title: `Invalid admin email`,
|
||||
message: () => `Invalid admin email. Please try again.`,
|
||||
title: "Invalid email",
|
||||
message: () => "Please enter a valid email.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
||||
title: `Invalid admin password`,
|
||||
message: () => `Invalid admin password. Please try again.`,
|
||||
title: "Invalid password",
|
||||
message: () => "Password must be at least 8 characters long.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
||||
title: `Email and password required`,
|
||||
message: () => `Email and password required. Please try again.`,
|
||||
title: "Required",
|
||||
message: () => "Please enter email and password.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
||||
title: `Authentication failed`,
|
||||
message: () => `Authentication failed. Please try again.`,
|
||||
title: "Authentication failed",
|
||||
message: () => "Please check your email and password and try again.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
||||
title: `Admin user already exists`,
|
||||
message: () => (
|
||||
<div>
|
||||
Admin user already exists.
|
||||
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||
Sign In
|
||||
</Link>
|
||||
now.
|
||||
</div>
|
||||
),
|
||||
title: "User already exists",
|
||||
message: () => "User already exists. Please sign in.",
|
||||
},
|
||||
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
||||
title: `Admin user does not exist`,
|
||||
message: () => (
|
||||
<div>
|
||||
Admin user does not exist.
|
||||
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||
Sign In
|
||||
</Link>
|
||||
now.
|
||||
</div>
|
||||
),
|
||||
title: "User does not exist",
|
||||
message: () => "User does not exist. Please sign up.",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -105,16 +92,28 @@ export const authErrorHandler = (
|
||||
errorCode: EAuthenticationErrorCodes,
|
||||
email?: string | undefined
|
||||
): TAuthErrorInfo | undefined => {
|
||||
const bannerAlertErrorCodes = [
|
||||
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
||||
const toastAlertErrorCodes = [
|
||||
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
|
||||
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
|
||||
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
|
||||
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_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_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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/lib/app-providers";
|
||||
import { StoreContext } from "@/lib/store-context";
|
||||
import { IInstanceStore } from "@/store/instance.store";
|
||||
|
||||
export const useInstance = (): IInstanceStore => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/lib/app-providers";
|
||||
import { StoreContext } from "@/lib/store-context";
|
||||
import { IThemeStore } from "@/store/theme.store";
|
||||
|
||||
export const useTheme = (): IThemeStore => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
// store
|
||||
import { StoreContext } from "@/lib/app-providers";
|
||||
import { StoreContext } from "@/lib/store-context";
|
||||
import { IUserStore } from "@/store/user.store";
|
||||
|
||||
export const useUser = (): IUserStore => {
|
||||
|
||||
@@ -1,47 +1,15 @@
|
||||
"use client";
|
||||
import { FC, ReactNode, useEffect } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { FC, ReactNode } from "react";
|
||||
// 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> = observer((props) => {
|
||||
export const AdminLayout: FC<TAdminLayout> = (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">
|
||||
@@ -53,4 +21,4 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
|
||||
<NewUserPopup />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ 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">
|
||||
@@ -30,7 +29,11 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
|
||||
</div>
|
||||
{!withoutBackground && (
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
|
||||
<Image
|
||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
||||
className="w-screen h-full object-cover"
|
||||
alt="Plane background pattern"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./default-layout";
|
||||
export * from "./admin-layout";
|
||||
@@ -1,43 +0,0 @@
|
||||
"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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { ReactElement, createContext } from "react";
|
||||
// mobx store
|
||||
import { RootStore } from "@/store/root-store";
|
||||
|
||||
export let rootStore = new RootStore();
|
||||
|
||||
export const StoreContext = createContext<RootStore>(rootStore);
|
||||
|
||||
const initializeStore = () => {
|
||||
const newRootStore = rootStore ?? new RootStore();
|
||||
if (typeof window === "undefined") return newRootStore;
|
||||
if (!rootStore) rootStore = newRootStore;
|
||||
return newRootStore;
|
||||
};
|
||||
|
||||
export const StoreProvider = ({ children }: { children: ReactElement }) => {
|
||||
const store = initializeStore();
|
||||
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
|
||||
};
|
||||
@@ -3,14 +3,14 @@
|
||||
import { FC, ReactNode, useEffect, Suspense } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { SWRConfig } from "swr";
|
||||
// hooks
|
||||
import { useTheme, useUser } from "@/hooks/store";
|
||||
// ui
|
||||
import { Toast } from "@plane/ui";
|
||||
// constants
|
||||
import { SWR_CONFIG } from "@/constants/swr-config";
|
||||
// helpers
|
||||
import { resolveGeneralTheme } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useTheme, useUser } from "@/hooks/store";
|
||||
import { resolveGeneralTheme } from "helpers/common.helper";
|
||||
|
||||
interface IAppWrapper {
|
||||
children: ReactNode;
|
||||
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
import { FC, ReactNode } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// hooks
|
||||
import { useInstance, useUser } from "@/hooks/store";
|
||||
// helpers
|
||||
import { EAuthenticationPageType } from "@/helpers";
|
||||
|
||||
export interface IAuthWrapper {
|
||||
children: ReactNode;
|
||||
authType?: EAuthenticationPageType;
|
||||
}
|
||||
|
||||
export const AuthWrapper: FC<IAuthWrapper> = observer((props) => {
|
||||
const router = useRouter();
|
||||
// props
|
||||
const { children, authType = EAuthenticationPageType.AUTHENTICATED } = props;
|
||||
// hooks
|
||||
const { instance } = useInstance();
|
||||
const { isLoading, currentUser, fetchCurrentUser } = useUser();
|
||||
|
||||
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
||||
shouldRetryOnError: false,
|
||||
});
|
||||
|
||||
if (isSWRLoading || isLoading)
|
||||
return (
|
||||
<div className="relative flex h-screen w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (authType === EAuthenticationPageType.NOT_AUTHENTICATED) {
|
||||
if (currentUser === undefined) return <>{children}</>;
|
||||
else {
|
||||
router.push("/general/");
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
if (authType === EAuthenticationPageType.AUTHENTICATED) {
|
||||
if (currentUser) return <>{children}</>;
|
||||
else {
|
||||
if (instance && instance?.instance?.is_setup_done) {
|
||||
router.push("/");
|
||||
return <></>;
|
||||
} else {
|
||||
router.push("/setup/");
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./app-wrapper";
|
||||
export * from "./instance-wrapper";
|
||||
export * from "./auth-wrapper";
|
||||
@@ -0,0 +1,65 @@
|
||||
"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/store";
|
||||
// helpers
|
||||
import { EInstancePageType } from "@/helpers";
|
||||
import { EmptyState } from "@/components/common";
|
||||
|
||||
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, instance, fetchInstanceInfo } = useInstance();
|
||||
|
||||
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false,
|
||||
revalidateOnReconnect: false,
|
||||
errorRetryCount: 0,
|
||||
});
|
||||
|
||||
if (isSWRLoading || isLoading)
|
||||
return (
|
||||
<div className="relative flex h-screen w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!instance) {
|
||||
return (
|
||||
<EmptyState
|
||||
title="Your instance wasn't configured successfully."
|
||||
description="Please try re-installing Plane to fix the problem. If the issue still persists please reach out to support@plane.so."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
|
||||
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
-2
@@ -14,7 +14,6 @@
|
||||
"@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",
|
||||
@@ -26,7 +25,7 @@
|
||||
"mobx-react-lite": "^4.0.5",
|
||||
"next": "^14.2.3",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss": "8.4.23",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.51.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,40 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,40 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
@@ -1,6 +1,6 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
// store
|
||||
// import { rootStore } from "@/lib/store-context";
|
||||
import { rootStore } from "@/lib/store-context";
|
||||
|
||||
export abstract class APIService {
|
||||
protected baseURL: string;
|
||||
@@ -17,14 +17,14 @@ export abstract class APIService {
|
||||
}
|
||||
|
||||
private setupInterceptors() {
|
||||
// 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);
|
||||
// }
|
||||
// );
|
||||
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>> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// helpers
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
// services
|
||||
import { APIService } from "services/api.service";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
type TCsrfTokenResponse = {
|
||||
csrf_token: string;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./auth.service";
|
||||
export * from "./instance.service";
|
||||
export * from "./user.service";
|
||||
@@ -13,7 +13,7 @@ export class InstanceService extends APIService {
|
||||
return this.get<IInstance>("/api/instances/")
|
||||
.then((response) => response.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
// helpers
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
// services
|
||||
import { APIService } from "services/api.service";
|
||||
// types
|
||||
import type { IUser } from "@plane/types";
|
||||
|
||||
interface IUserSession extends IUser {
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
// helpers
|
||||
import { API_BASE_URL } from "helpers/common.helper";
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import set from "lodash/set";
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
import set from "lodash/set";
|
||||
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,12 +18,11 @@ 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<IInstanceConfiguration[]>;
|
||||
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<void>;
|
||||
}
|
||||
|
||||
export class InstanceStore implements IInstanceStore {
|
||||
@@ -46,7 +45,6 @@ export class InstanceStore implements IInstanceStore {
|
||||
// computed
|
||||
formattedConfig: computed,
|
||||
// actions
|
||||
hydrate: action,
|
||||
fetchInstanceInfo: action,
|
||||
fetchInstanceAdmins: action,
|
||||
updateInstanceInfo: action,
|
||||
@@ -57,10 +55,6 @@ 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.
|
||||
@@ -154,15 +148,13 @@ export class InstanceStore implements IInstanceStore {
|
||||
*/
|
||||
updateInstanceConfigurations = async (data: Partial<IFormattedInstanceConfiguration>) => {
|
||||
try {
|
||||
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;
|
||||
await this.instanceService.updateInstanceConfigurations(data).then((response) => {
|
||||
runInAction(() => {
|
||||
this.instanceConfigurations = this.instanceConfigurations
|
||||
? [...this.instanceConfigurations, ...response]
|
||||
: response;
|
||||
});
|
||||
});
|
||||
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 { IInstanceStore, InstanceStore } from "./instance.store";
|
||||
import { IThemeStore, ThemeStore } from "./theme.store";
|
||||
import { IInstanceStore, InstanceStore } from "./instance.store";
|
||||
import { IUserStore, UserStore } from "./user.store";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
@@ -17,14 +17,9 @@ 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() {
|
||||
localStorage.setItem("theme", "system");
|
||||
|
||||
this.instance = new InstanceStore(this);
|
||||
this.user = new UserStore(this);
|
||||
this.theme = new ThemeStore(this);
|
||||
@@ -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,7 +9,6 @@ export interface IThemeStore {
|
||||
theme: string | undefined;
|
||||
isSidebarCollapsed: boolean | undefined;
|
||||
// actions
|
||||
hydrate: (data: any) => void;
|
||||
toggleNewUserPopup: () => void;
|
||||
toggleSidebar: (collapsed: boolean) => void;
|
||||
setTheme: (currentTheme: TTheme) => void;
|
||||
@@ -34,10 +33,6 @@ export class ThemeStore implements IThemeStore {
|
||||
});
|
||||
}
|
||||
|
||||
hydrate = (data: any) => {
|
||||
if (data) this.theme = data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Toggle the new user popup modal
|
||||
*/
|
||||
|
||||
@@ -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 { RootStore } from "@/store/root-store";
|
||||
import { AuthService } from "@/services";
|
||||
|
||||
export interface IUserStore {
|
||||
// observables
|
||||
@@ -15,7 +15,6 @@ export interface IUserStore {
|
||||
isUserLoggedIn: boolean | undefined;
|
||||
currentUser: IUser | undefined;
|
||||
// fetch actions
|
||||
hydrate: (data: any) => void;
|
||||
fetchCurrentUser: () => Promise<IUser>;
|
||||
reset: () => void;
|
||||
signOut: () => void;
|
||||
@@ -47,10 +46,6 @@ export class UserStore implements IUserStore {
|
||||
this.authService = new AuthService();
|
||||
}
|
||||
|
||||
hydrate = (data: any) => {
|
||||
if (data) this.currentUser = data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Fetches the current user
|
||||
* @returns Promise<IUser>
|
||||
|
||||
@@ -315,7 +315,7 @@ class IssueLinkSerializer(BaseSerializer):
|
||||
if IssueLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
issue_id=instance.issue_id,
|
||||
).exclude(pk=instance.id).exists():
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
)
|
||||
|
||||
@@ -462,7 +462,7 @@ class IssueLinkSerializer(BaseSerializer):
|
||||
if IssueLink.objects.filter(
|
||||
url=validated_data.get("url"),
|
||||
issue_id=instance.issue_id,
|
||||
).exclude(pk=instance.id).exists():
|
||||
).exists():
|
||||
raise serializers.ValidationError(
|
||||
{"error": "URL already exists for this Issue"}
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ from plane.app.views import (
|
||||
UserEndpoint,
|
||||
UserIssueCompletedGraphEndpoint,
|
||||
UserWorkspaceDashboardEndpoint,
|
||||
UserSessionEndpoint,
|
||||
## End User
|
||||
## Workspaces
|
||||
UserWorkSpacesEndpoint,
|
||||
@@ -30,11 +29,6 @@ urlpatterns = [
|
||||
),
|
||||
name="users",
|
||||
),
|
||||
path(
|
||||
"users/session/",
|
||||
UserSessionEndpoint.as_view(),
|
||||
name="user-session",
|
||||
),
|
||||
path(
|
||||
"users/me/settings/",
|
||||
UserEndpoint.as_view(
|
||||
|
||||
@@ -222,4 +222,4 @@ from .error_404 import custom_404_view
|
||||
|
||||
from .exporter.base import ExportIssuesEndpoint
|
||||
from .notification.base import MarkAllReadNotificationViewSet
|
||||
from .user.base import AccountEndpoint, ProfileEndpoint, UserSessionEndpoint
|
||||
from .user.base import AccountEndpoint, ProfileEndpoint
|
||||
|
||||
@@ -241,7 +241,6 @@ class CycleViewSet(BaseViewSet):
|
||||
"backlog_issues",
|
||||
"assignee_ids",
|
||||
"status",
|
||||
"created_by"
|
||||
)
|
||||
|
||||
if data:
|
||||
@@ -366,7 +365,6 @@ class CycleViewSet(BaseViewSet):
|
||||
"backlog_issues",
|
||||
"assignee_ids",
|
||||
"status",
|
||||
"created_by",
|
||||
)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -566,7 +564,6 @@ class CycleViewSet(BaseViewSet):
|
||||
"backlog_issues",
|
||||
"assignee_ids",
|
||||
"status",
|
||||
"created_by",
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.utils import timezone
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
# Module imports
|
||||
from plane.app.serializers import (
|
||||
@@ -181,25 +180,6 @@ class UserEndpoint(BaseViewSet):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class UserSessionEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
if request.user.is_authenticated:
|
||||
user = User.objects.get(pk=request.user.id)
|
||||
serializer = UserMeSerializer(user)
|
||||
data = {"is_authenticated": True}
|
||||
data["user"] = serializer.data
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
{"is_authenticated": False}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
@@ -269,7 +249,6 @@ class ProfileEndpoint(BaseAPIView):
|
||||
serializer = ProfileSerializer(profile)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache("/api/users/me/settings/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user=request.user)
|
||||
serializer = ProfileSerializer(
|
||||
|
||||
@@ -96,7 +96,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||
@invalidate_cache(path="/api/instances/", user=False)
|
||||
def create(self, request):
|
||||
try:
|
||||
serializer = WorkSpaceSerializer(data=request.data)
|
||||
@@ -152,12 +151,8 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||
)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/settings/", multiple=True, user=False
|
||||
)
|
||||
@invalidate_cache(path="/api/users/me/workspaces/", multiple=True, user=False)
|
||||
@invalidate_cache(path="/api/users/me/settings/", multiple=True, user=False)
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -100,12 +100,6 @@ class Adapter:
|
||||
user.save()
|
||||
Profile.objects.create(user=user)
|
||||
|
||||
if not user.is_active:
|
||||
raise AuthenticationException(
|
||||
AUTHENTICATION_ERROR_CODES["USER_ACCOUNT_DEACTIVATED"],
|
||||
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||
)
|
||||
|
||||
# Update user details
|
||||
user.last_login_medium = self.provider
|
||||
user.last_active = timezone.now()
|
||||
|
||||
@@ -4,9 +4,6 @@ AUTHENTICATION_ERROR_CODES = {
|
||||
"INVALID_EMAIL": 5005,
|
||||
"EMAIL_REQUIRED": 5010,
|
||||
"SIGNUP_DISABLED": 5015,
|
||||
"MAGIC_LINK_LOGIN_DISABLED": 5016,
|
||||
"PASSWORD_LOGIN_DISABLED": 5018,
|
||||
"USER_ACCOUNT_DEACTIVATED": 5019,
|
||||
# Password strength
|
||||
"INVALID_PASSWORD": 5020,
|
||||
"SMTP_NOT_CONFIGURED": 5025,
|
||||
@@ -38,7 +35,6 @@ AUTHENTICATION_ERROR_CODES = {
|
||||
"EXPIRED_PASSWORD_TOKEN": 5130,
|
||||
# Change password
|
||||
"INCORRECT_OLD_PASSWORD": 5135,
|
||||
"MISSING_PASSWORD": 5138,
|
||||
"INVALID_NEW_PASSWORD": 5140,
|
||||
# set passowrd
|
||||
"PASSWORD_ALREADY_SET": 5145,
|
||||
@@ -51,7 +47,6 @@ AUTHENTICATION_ERROR_CODES = {
|
||||
"ADMIN_AUTHENTICATION_FAILED": 5175,
|
||||
"ADMIN_USER_ALREADY_EXIST": 5180,
|
||||
"ADMIN_USER_DOES_NOT_EXIST": 5185,
|
||||
"ADMIN_USER_DEACTIVATED": 5190,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# Python imports
|
||||
import os
|
||||
|
||||
# Module imports
|
||||
from plane.authentication.adapter.credential import CredentialAdapter
|
||||
from plane.db.models import User
|
||||
@@ -8,7 +5,6 @@ from plane.authentication.adapter.error import (
|
||||
AUTHENTICATION_ERROR_CODES,
|
||||
AuthenticationException,
|
||||
)
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
|
||||
|
||||
class EmailProvider(CredentialAdapter):
|
||||
@@ -27,21 +23,6 @@ class EmailProvider(CredentialAdapter):
|
||||
self.code = code
|
||||
self.is_signup = is_signup
|
||||
|
||||
(ENABLE_EMAIL_PASSWORD,) = get_configuration_value(
|
||||
[
|
||||
{
|
||||
"key": "ENABLE_EMAIL_PASSWORD",
|
||||
"default": os.environ.get("ENABLE_EMAIL_PASSWORD"),
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
if ENABLE_EMAIL_PASSWORD == "0":
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["ENABLE_EMAIL_PASSWORD"],
|
||||
error_message="ENABLE_EMAIL_PASSWORD",
|
||||
)
|
||||
|
||||
def set_user_data(self):
|
||||
if self.is_signup:
|
||||
# Check if the user already exists
|
||||
|
||||
@@ -26,20 +26,23 @@ class MagicCodeProvider(CredentialAdapter):
|
||||
code=None,
|
||||
):
|
||||
|
||||
(
|
||||
EMAIL_HOST,
|
||||
ENABLE_MAGIC_LINK_LOGIN,
|
||||
) = get_configuration_value(
|
||||
[
|
||||
{
|
||||
"key": "EMAIL_HOST",
|
||||
"default": os.environ.get("EMAIL_HOST"),
|
||||
},
|
||||
{
|
||||
"key": "ENABLE_MAGIC_LINK_LOGIN",
|
||||
"default": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "1"),
|
||||
},
|
||||
]
|
||||
(EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD) = (
|
||||
get_configuration_value(
|
||||
[
|
||||
{
|
||||
"key": "EMAIL_HOST",
|
||||
"default": os.environ.get("EMAIL_HOST"),
|
||||
},
|
||||
{
|
||||
"key": "EMAIL_HOST_USER",
|
||||
"default": os.environ.get("EMAIL_HOST_USER"),
|
||||
},
|
||||
{
|
||||
"key": "EMAIL_HOST_PASSWORD",
|
||||
"default": os.environ.get("EMAIL_HOST_PASSWORD"),
|
||||
},
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if not (EMAIL_HOST):
|
||||
@@ -49,15 +52,6 @@ class MagicCodeProvider(CredentialAdapter):
|
||||
payload={"email": str(self.key)},
|
||||
)
|
||||
|
||||
if ENABLE_MAGIC_LINK_LOGIN == "0":
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"MAGIC_LINK_LOGIN_DISABLED"
|
||||
],
|
||||
error_message="MAGIC_LINK_LOGIN_DISABLED",
|
||||
payload={"email": str(self.key)},
|
||||
)
|
||||
|
||||
super().__init__(request, self.provider)
|
||||
self.key = key
|
||||
self.code = code
|
||||
@@ -135,10 +129,8 @@ class MagicCodeProvider(CredentialAdapter):
|
||||
payload={"email": str(email)},
|
||||
)
|
||||
else:
|
||||
magic_key = str(self.key)
|
||||
email = magic_key.replace("magic_", "", 1)
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["EXPIRED_MAGIC_CODE"],
|
||||
error_message="EXPIRED_MAGIC_CODE",
|
||||
payload={"email": str(email)},
|
||||
payload={"email": str(self.key)},
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Django imports
|
||||
from django.contrib.auth import login
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from plane.authentication.utils.host import base_host
|
||||
@@ -8,11 +7,6 @@ from plane.authentication.utils.host import base_host
|
||||
|
||||
def user_login(request, user, is_app=False, is_admin=False, is_space=False):
|
||||
login(request=request, user=user)
|
||||
|
||||
# If is admin cookie set the custom age
|
||||
if is_admin:
|
||||
request.session.set_expiry(settings.ADMIN_SESSION_COOKIE_AGE)
|
||||
|
||||
device_info = {
|
||||
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
|
||||
"ip_address": request.META.get("REMOTE_ADDR", ""),
|
||||
|
||||
@@ -24,61 +24,62 @@ class EmailCheckSignUpEndpoint(APIView):
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
# Check instance configuration
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"INSTANCE_NOT_CONFIGURED"
|
||||
],
|
||||
error_message="INSTANCE_NOT_CONFIGURED",
|
||||
)
|
||||
email = request.data.get("email", False)
|
||||
|
||||
# Return error if email is not present
|
||||
if not email:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
||||
error_message="EMAIL_REQUIRED",
|
||||
)
|
||||
|
||||
# Validate email
|
||||
validate_email(email)
|
||||
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
if existing_user:
|
||||
# check if the account is the deactivated
|
||||
if not existing_user.is_active:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"USER_ACCOUNT_DEACTIVATED"
|
||||
],
|
||||
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||
)
|
||||
|
||||
# Raise user already exist
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"USER_ALREADY_EXIST"
|
||||
],
|
||||
error_message="USER_ALREADY_EXIST",
|
||||
)
|
||||
return Response(
|
||||
{"status": True},
|
||||
status=status.HTTP_200_OK,
|
||||
# Check instance configuration
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"INSTANCE_NOT_CONFIGURED"
|
||||
],
|
||||
error_message="INSTANCE_NOT_CONFIGURED",
|
||||
)
|
||||
return Response(
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
email = request.data.get("email", False)
|
||||
|
||||
# Return error if email is not present
|
||||
if not email:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
||||
error_message="EMAIL_REQUIRED",
|
||||
)
|
||||
return Response(
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Validate email
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
raise AuthenticationException(
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
|
||||
error_message="INVALID_EMAIL",
|
||||
)
|
||||
except AuthenticationException as e:
|
||||
return Response(
|
||||
e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
if existing_user:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||
error_message="USER_ALREADY_EXIST",
|
||||
)
|
||||
return Response(
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response(
|
||||
{"status": True},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class EmailCheckSignInEndpoint(APIView):
|
||||
|
||||
@@ -87,61 +88,61 @@ class EmailCheckSignInEndpoint(APIView):
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
# Check instance configuration
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"INSTANCE_NOT_CONFIGURED"
|
||||
],
|
||||
error_message="INSTANCE_NOT_CONFIGURED",
|
||||
)
|
||||
|
||||
email = request.data.get("email", False)
|
||||
|
||||
# Return error if email is not present
|
||||
if not email:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
||||
error_message="EMAIL_REQUIRED",
|
||||
)
|
||||
|
||||
# Validate email
|
||||
validate_email(email)
|
||||
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
# If existing user
|
||||
if existing_user:
|
||||
# Raise different exception when user is not active
|
||||
if not existing_user.is_active:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"USER_ACCOUNT_DEACTIVATED"
|
||||
],
|
||||
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||
)
|
||||
# Return true
|
||||
return Response(
|
||||
{
|
||||
"status": True,
|
||||
"is_password_autoset": existing_user.is_password_autoset,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
# Raise error
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||
error_message="USER_DOES_NOT_EXIST",
|
||||
# Check instance configuration
|
||||
instance = Instance.objects.first()
|
||||
if instance is None or not instance.is_setup_done:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"INSTANCE_NOT_CONFIGURED"
|
||||
],
|
||||
error_message="INSTANCE_NOT_CONFIGURED",
|
||||
)
|
||||
return Response(
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
email = request.data.get("email", False)
|
||||
|
||||
# Return error if email is not present
|
||||
if not email:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
||||
error_message="EMAIL_REQUIRED",
|
||||
)
|
||||
return Response(
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Validate email
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
raise AuthenticationException(
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
|
||||
error_message="INVALID_EMAIL",
|
||||
)
|
||||
except AuthenticationException as e:
|
||||
return Response(
|
||||
e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
if existing_user:
|
||||
return Response(
|
||||
{
|
||||
"status": True,
|
||||
"is_password_autoset": existing_user.is_password_autoset,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||
error_message="USER_DOES_NOT_EXIST",
|
||||
)
|
||||
return Response(
|
||||
exc.get_error_dict(),
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -90,9 +90,7 @@ class SignInAuthEndpoint(View):
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
if not existing_user:
|
||||
if not User.objects.filter(email=email).exists():
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||
error_message="USER_DOES_NOT_EXIST",
|
||||
@@ -107,22 +105,6 @@ class SignInAuthEndpoint(View):
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
if not existing_user.is_active:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"USER_ACCOUNT_DEACTIVATED"
|
||||
],
|
||||
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||
)
|
||||
params = exc.get_error_dict()
|
||||
if next_path:
|
||||
params["next_path"] = str(next_path)
|
||||
url = urljoin(
|
||||
base_host(request=request, is_app=True),
|
||||
"sign-in?" + urlencode(params),
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
try:
|
||||
provider = EmailProvider(
|
||||
request=request, key=email, code=password, is_signup=False
|
||||
@@ -215,27 +197,7 @@ class SignUpAuthEndpoint(View):
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Existing user
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
if existing_user:
|
||||
# Existing User
|
||||
if not existing_user.is_active:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"USER_ACCOUNT_DEACTIVATED"
|
||||
],
|
||||
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||
)
|
||||
params = exc.get_error_dict()
|
||||
if next_path:
|
||||
params["next_path"] = str(next_path)
|
||||
url = urljoin(
|
||||
base_host(request=request, is_app=True),
|
||||
"?" + urlencode(params),
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
if User.objects.filter(email=email).exists():
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||
error_message="USER_ALREADY_EXIST",
|
||||
|
||||
@@ -95,10 +95,7 @@ class MagicSignInEndpoint(View):
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Existing User
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
|
||||
if not existing_user:
|
||||
if not User.objects.filter(email=email).exists():
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||
error_message="USER_DOES_NOT_EXIST",
|
||||
@@ -112,22 +109,6 @@ class MagicSignInEndpoint(View):
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
if not existing_user.is_active:
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES[
|
||||
"USER_ACCOUNT_DEACTIVATED"
|
||||
],
|
||||
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||
)
|
||||
params = exc.get_error_dict()
|
||||
if next_path:
|
||||
params["next_path"] = str(next_path)
|
||||
url = urljoin(
|
||||
base_host(request=request, is_app=True),
|
||||
"sign-in?" + urlencode(params),
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
try:
|
||||
provider = MagicCodeProvider(
|
||||
request=request, key=f"magic_{email}", code=code
|
||||
@@ -145,7 +126,7 @@ class MagicSignInEndpoint(View):
|
||||
path = (
|
||||
str(next_path)
|
||||
if next_path
|
||||
else str(get_redirection_path(user=user))
|
||||
else str(process_workspace_project_invitations(user=user))
|
||||
)
|
||||
# redirect to referer path
|
||||
url = urljoin(base_host(request=request, is_app=True), path)
|
||||
@@ -186,9 +167,8 @@ class MagicSignUpEndpoint(View):
|
||||
"?" + urlencode(params),
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
# Existing user
|
||||
existing_user = User.objects.filter(email=email).first()
|
||||
if existing_user:
|
||||
|
||||
if User.objects.filter(email=email).exists():
|
||||
exc = AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||
error_message="USER_ALREADY_EXIST",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user