diff --git a/admin/.eslintrc.js b/admin/.eslintrc.js index 2278de30f..a82c768a0 100644 --- a/admin/.eslintrc.js +++ b/admin/.eslintrc.js @@ -10,5 +10,43 @@ module.exports = { }, }, }, - rules: {} + rules: { + "import/order": [ + "error", + { + groups: ["builtin", "external", "internal", "parent", "sibling",], + pathGroups: [ + { + pattern: "react", + group: "external", + position: "before", + }, + { + pattern: "lucide-react", + group: "external", + position: "after", + }, + { + pattern: "@headlessui/**", + group: "external", + position: "after", + }, + { + pattern: "@plane/**", + group: "external", + position: "after", + }, + { + pattern: "@/**", + group: "internal", + } + ], + pathGroupsExcludedImportTypes: ["builtin", "internal", "react"], + alphabetize: { + order: "asc", + caseInsensitive: true, + }, + }, + ], + }, } \ No newline at end of file diff --git a/admin/app/ai/components/index.ts b/admin/app/ai/components/index.ts deleted file mode 100644 index 2a7609401..000000000 --- a/admin/app/ai/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ai-config-form"; diff --git a/admin/app/ai/components/ai-config-form.tsx b/admin/app/ai/form.tsx similarity index 97% rename from admin/app/ai/components/ai-config-form.tsx rename to admin/app/ai/form.tsx index fda70611c..cec5c0748 100644 --- a/admin/app/ai/components/ai-config-form.tsx +++ b/admin/app/ai/form.tsx @@ -1,10 +1,10 @@ import { FC } from "react"; import { useForm } from "react-hook-form"; import { Lightbulb } from "lucide-react"; -import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // components -import { ControllerInput, TControllerInputFormField } from "components/common"; +import { ControllerInput, TControllerInputFormField } from "@/components/common"; // hooks import { useInstance } from "@/hooks/store"; diff --git a/admin/app/ai/layout.tsx b/admin/app/ai/layout.tsx index 61df8ebd9..e3fd537bc 100644 --- a/admin/app/ai/layout.tsx +++ b/admin/app/ai/layout.tsx @@ -2,20 +2,8 @@ import { ReactNode } from "react"; // layouts -import { AdminLayout } from "@/layouts"; -// lib -import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; +import { AdminLayout } from "@/layouts/admin-layout"; -interface AILayoutProps { - children: ReactNode; +export default function AILayout({ children }: { children: ReactNode }) { + return {children}; } - -const AILayout = ({ children }: AILayoutProps) => ( - - - {children} - - -); - -export default AILayout; diff --git a/admin/app/ai/page.tsx b/admin/app/ai/page.tsx index 5d002ca55..0979bbabe 100644 --- a/admin/app/ai/page.tsx +++ b/admin/app/ai/page.tsx @@ -1,13 +1,14 @@ "use client"; -import useSWR from "swr"; import { observer } from "mobx-react-lite"; +import useSWR from "swr"; import { Loader } from "@plane/ui"; // components import { PageHeader } from "@/components/core"; -import { InstanceAIForm } from "./components"; // hooks import { useInstance } from "@/hooks/store"; +// components +import { InstanceAIForm } from "./form"; const InstanceAIPage = observer(() => { // store diff --git a/admin/app/authentication/components/common/authentication-method-card.tsx b/admin/app/authentication/components/authentication-method-card.tsx similarity index 100% rename from admin/app/authentication/components/common/authentication-method-card.tsx rename to admin/app/authentication/components/authentication-method-card.tsx diff --git a/admin/app/authentication/components/common/index.ts b/admin/app/authentication/components/common/index.ts deleted file mode 100644 index 0f5713cdb..000000000 --- a/admin/app/authentication/components/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./authentication-method-card"; diff --git a/admin/app/authentication/components/email-config-switch.tsx b/admin/app/authentication/components/email-config-switch.tsx index 9c23901fe..0f09cf82c 100644 --- a/admin/app/authentication/components/email-config-switch.tsx +++ b/admin/app/authentication/components/email-config-switch.tsx @@ -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; diff --git a/admin/app/authentication/github/components/root.tsx b/admin/app/authentication/components/github-config.tsx similarity index 97% rename from admin/app/authentication/github/components/root.tsx rename to admin/app/authentication/components/github-config.tsx index d820bc8a2..27264d460 100644 --- a/admin/app/authentication/github/components/root.tsx +++ b/admin/app/authentication/components/github-config.tsx @@ -1,18 +1,18 @@ "use client"; import React from "react"; -import Link from "next/link"; import { observer } from "mobx-react-lite"; -// hooks -import { useInstance } from "@/hooks/store"; -// ui -import { ToggleSwitch, getButtonStyling } from "@plane/ui"; +import Link from "next/link"; // icons import { Settings2 } from "lucide-react"; // types import { TInstanceAuthenticationMethodKeys } from "@plane/types"; +// ui +import { ToggleSwitch, getButtonStyling } from "@plane/ui"; // helpers -import { cn } from "helpers/common.helper"; +import { cn } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; type Props = { disabled: boolean; diff --git a/admin/app/authentication/google/components/root.tsx b/admin/app/authentication/components/google-config.tsx similarity index 97% rename from admin/app/authentication/google/components/root.tsx rename to admin/app/authentication/components/google-config.tsx index 5432c95bf..9fde70dac 100644 --- a/admin/app/authentication/google/components/root.tsx +++ b/admin/app/authentication/components/google-config.tsx @@ -1,18 +1,18 @@ "use client"; import React from "react"; -import Link from "next/link"; import { observer } from "mobx-react-lite"; -// hooks -import { useInstance } from "@/hooks/store"; -// ui -import { ToggleSwitch, getButtonStyling } from "@plane/ui"; +import Link from "next/link"; // icons import { Settings2 } from "lucide-react"; // types import { TInstanceAuthenticationMethodKeys } from "@plane/types"; +// ui +import { ToggleSwitch, getButtonStyling } from "@plane/ui"; // helpers -import { cn } from "helpers/common.helper"; +import { cn } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; type Props = { disabled: boolean; diff --git a/admin/app/authentication/components/index.ts b/admin/app/authentication/components/index.ts index 59760f00d..d76d61f57 100644 --- a/admin/app/authentication/components/index.ts +++ b/admin/app/authentication/components/index.ts @@ -1,3 +1,5 @@ -export * from "./common"; export * from "./email-config-switch"; export * from "./password-config-switch"; +export * from "./authentication-method-card"; +export * from "./github-config"; +export * from "./google-config"; diff --git a/admin/app/authentication/components/password-config-switch.tsx b/admin/app/authentication/components/password-config-switch.tsx index ce33cd329..901cce862 100644 --- a/admin/app/authentication/components/password-config-switch.tsx +++ b/admin/app/authentication/components/password-config-switch.tsx @@ -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; diff --git a/admin/app/authentication/github/components/index.ts b/admin/app/authentication/github/components/index.ts deleted file mode 100644 index e9e36e988..000000000 --- a/admin/app/authentication/github/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./root"; -export * from "./github-config-form"; \ No newline at end of file diff --git a/admin/app/authentication/github/components/github-config-form.tsx b/admin/app/authentication/github/form.tsx similarity index 90% rename from admin/app/authentication/github/components/github-config-form.tsx rename to admin/app/authentication/github/form.tsx index 43d220575..75c76e7a5 100644 --- a/admin/app/authentication/github/components/github-config-form.tsx +++ b/admin/app/authentication/github/form.tsx @@ -1,8 +1,9 @@ import { FC, useState } from "react"; -import { useForm } from "react-hook-form"; +import isEmpty from "lodash/isEmpty"; import Link from "next/link"; -// hooks -import { useInstance } from "@/hooks/store"; +import { useForm } from "react-hook-form"; +// types +import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types"; // ui import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; // components @@ -12,12 +13,11 @@ import { CopyField, TControllerInputFormField, TCopyField, -} from "components/common"; -// types -import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types"; +} from "@/components/common"; // helpers -import { API_BASE_URL, cn } from "helpers/common.helper"; -import isEmpty from "lodash/isEmpty"; +import { API_BASE_URL, cn } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; type Props = { config: IFormattedInstanceConfiguration; @@ -46,7 +46,7 @@ export const InstanceGithubConfigForm: FC = (props) => { const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : ""; - const githubFormFields: TControllerInputFormField[] = [ + const GITHUB_FORM_FIELDS: TControllerInputFormField[] = [ { key: "GITHUB_CLIENT_ID", type: "text", @@ -55,6 +55,7 @@ export const InstanceGithubConfigForm: FC = (props) => { <> You will get this from your{" "} = (props) => { <> Your client secret is also found in your{" "} = (props) => { }, ]; - const githubCopyFields: TCopyField[] = [ + const GITHUB_SERVICE_FIELD: TCopyField[] = [ { key: "Origin_URL", label: "Origin URL", @@ -100,6 +102,7 @@ export const InstanceGithubConfigForm: FC = (props) => { <> We will auto-generate this. Paste this into the Authorized origin URL field{" "} = (props) => { <> We will auto-generate this. Paste this into your Authorized Callback URI field{" "} = (props) => { const payload: Partial = { ...formData }; await updateInstanceConfigurations(payload) - .then(() => { + .then((response = []) => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Success", message: "Github Configuration Settings updated successfully", }); - reset(); + reset({ + GITHUB_CLIENT_ID: response.find((item) => item.key === "GITHUB_CLIENT_ID")?.value, + GITHUB_CLIENT_SECRET: response.find((item) => item.key === "GITHUB_CLIENT_SECRET")?.value, + }); }) .catch((err) => console.error(err)); }; @@ -163,7 +170,7 @@ export const InstanceGithubConfigForm: FC = (props) => {
Configuration
- {githubFormFields.map((field) => ( + {GITHUB_FORM_FIELDS.map((field) => ( = (props) => {
Service provider details
- {githubCopyFields.map((field) => ( + {GITHUB_SERVICE_FIELD.map((field) => ( ))}
diff --git a/admin/app/authentication/github/page.tsx b/admin/app/authentication/github/page.tsx index 893762d47..b65b99205 100644 --- a/admin/app/authentication/github/page.tsx +++ b/admin/app/authentication/github/page.tsx @@ -1,22 +1,23 @@ "use client"; import { useState } from "react"; +import { observer } from "mobx-react-lite"; import Image from "next/image"; import { useTheme } from "next-themes"; -import { observer } from "mobx-react-lite"; import useSWR from "swr"; import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui"; // components import { PageHeader } from "@/components/core"; -import { AuthenticationMethodCard } from "../components"; -import { InstanceGithubConfigForm } from "./components"; -// hooks -import { useInstance } from "@/hooks/store"; // helpers import { resolveGeneralTheme } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; // icons import githubLightModeImage from "@/public/logos/github-black.png"; import githubDarkModeImage from "@/public/logos/github-white.png"; +// local components +import { AuthenticationMethodCard } from "../components"; +import { InstanceGithubConfigForm } from "./form"; const InstanceGithubAuthenticationPage = observer(() => { // store diff --git a/admin/app/authentication/google/components/index.ts b/admin/app/authentication/google/components/index.ts deleted file mode 100644 index d0d37f305..000000000 --- a/admin/app/authentication/google/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./root"; -export * from "./google-config-form"; \ No newline at end of file diff --git a/admin/app/authentication/google/components/google-config-form.tsx b/admin/app/authentication/google/form.tsx similarity index 91% rename from admin/app/authentication/google/components/google-config-form.tsx rename to admin/app/authentication/google/form.tsx index f07021694..fd2e7c73c 100644 --- a/admin/app/authentication/google/components/google-config-form.tsx +++ b/admin/app/authentication/google/form.tsx @@ -1,8 +1,9 @@ import { FC, useState } from "react"; -import { useForm } from "react-hook-form"; +import isEmpty from "lodash/isEmpty"; import Link from "next/link"; -// hooks -import { useInstance } from "@/hooks/store"; +import { useForm } from "react-hook-form"; +// types +import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types"; // ui import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; // components @@ -12,12 +13,11 @@ import { CopyField, TControllerInputFormField, TCopyField, -} from "components/common"; -// types -import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types"; +} from "@/components/common"; // helpers -import { API_BASE_URL, cn } from "helpers/common.helper"; -import isEmpty from "lodash/isEmpty"; +import { API_BASE_URL, cn } from "@/helpers/common.helper"; +// hooks +import { useInstance } from "@/hooks/store"; type Props = { config: IFormattedInstanceConfiguration; @@ -46,7 +46,7 @@ export const InstanceGoogleConfigForm: FC = (props) => { const originURL = !isEmpty(API_BASE_URL) ? API_BASE_URL : typeof window !== "undefined" ? window.location.origin : ""; - const googleFormFields: TControllerInputFormField[] = [ + const GOOGLE_FORM_FIELDS: TControllerInputFormField[] = [ { key: "GOOGLE_CLIENT_ID", type: "text", @@ -55,6 +55,7 @@ export const InstanceGoogleConfigForm: FC = (props) => { <> Your client ID lives in your Google API Console.{" "}
= (props) => { <> Your client secret should also be in your Google API Console.{" "} = (props) => { }, ]; - const googleCopyFeilds: TCopyField[] = [ + const GOOGLE_SERVICE_DETAILS: TCopyField[] = [ { key: "Origin_URL", label: "Origin URL", @@ -134,13 +136,16 @@ export const InstanceGoogleConfigForm: FC = (props) => { const payload: Partial = { ...formData }; await updateInstanceConfigurations(payload) - .then(() => { + .then((response = []) => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Success", message: "Google Configuration Settings updated successfully", }); - reset(); + reset({ + GOOGLE_CLIENT_ID: response.find((item) => item.key === "GOOGLE_CLIENT_ID")?.value, + GOOGLE_CLIENT_SECRET: response.find((item) => item.key === "GOOGLE_CLIENT_SECRET")?.value, + }); }) .catch((err) => console.error(err)); }; @@ -163,7 +168,7 @@ export const InstanceGoogleConfigForm: FC = (props) => {
Configuration
- {googleFormFields.map((field) => ( + {GOOGLE_FORM_FIELDS.map((field) => ( = (props) => {
Service provider details
- {googleCopyFeilds.map((field) => ( + {GOOGLE_SERVICE_DETAILS.map((field) => ( ))}
diff --git a/admin/app/authentication/google/page.tsx b/admin/app/authentication/google/page.tsx index 9b02842af..05117dbe3 100644 --- a/admin/app/authentication/google/page.tsx +++ b/admin/app/authentication/google/page.tsx @@ -1,18 +1,19 @@ "use client"; import { useState } from "react"; -import Image from "next/image"; import { observer } from "mobx-react-lite"; +import Image from "next/image"; import useSWR from "swr"; import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui"; // components import { PageHeader } from "@/components/core"; -import { AuthenticationMethodCard } from "../components"; -import { InstanceGoogleConfigForm } from "./components"; // hooks import { useInstance } from "@/hooks/store"; // icons import GoogleLogo from "@/public/logos/google-logo.svg"; +// local components +import { AuthenticationMethodCard } from "../components"; +import { InstanceGoogleConfigForm } from "./form"; const InstanceGoogleAuthenticationPage = observer(() => { // store diff --git a/admin/app/authentication/layout.tsx b/admin/app/authentication/layout.tsx index c6f146ff5..2568859db 100644 --- a/admin/app/authentication/layout.tsx +++ b/admin/app/authentication/layout.tsx @@ -2,20 +2,8 @@ import { ReactNode } from "react"; // layouts -import { AdminLayout } from "@/layouts"; -// lib -import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; +import { AdminLayout } from "@/layouts/admin-layout"; -interface AuthenticationLayoutProps { - children: ReactNode; +export default function AuthenticationLayout({ children }: { children: ReactNode }) { + return {children}; } - -const AuthenticationLayout = ({ children }: AuthenticationLayoutProps) => ( - - - {children} - - -); - -export default AuthenticationLayout; diff --git a/admin/app/authentication/page.tsx b/admin/app/authentication/page.tsx index 068592468..25be147ca 100644 --- a/admin/app/authentication/page.tsx +++ b/admin/app/authentication/page.tsx @@ -1,26 +1,31 @@ "use client"; import { useState } from "react"; +import { observer } from "mobx-react-lite"; import Image from "next/image"; import { useTheme } from "next-themes"; import useSWR from "swr"; -import { observer } from "mobx-react-lite"; import { Mails, KeyRound } from "lucide-react"; -import { Loader, setPromiseToast } from "@plane/ui"; import { TInstanceConfigurationKeys } from "@plane/types"; +import { Loader, setPromiseToast } from "@plane/ui"; // components -import { AuthenticationMethodCard, EmailCodesConfiguration, PasswordLoginConfiguration } from "./components"; -import { GoogleConfiguration } from "./google/components"; -import { GithubConfiguration } from "./github/components"; import { PageHeader } from "@/components/core"; // hooks -import { useInstance } from "@/hooks/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; diff --git a/admin/app/email/components/email-config-form.tsx b/admin/app/email/components/email-config-form.tsx index 50c867132..eb73e18b9 100644 --- a/admin/app/email/components/email-config-form.tsx +++ b/admin/app/email/components/email-config-form.tsx @@ -1,14 +1,14 @@ import React, { FC, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; // hooks +import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types"; +import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; import { useInstance } from "@/hooks/store"; // ui -import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui"; // components import { ControllerInput, TControllerInputFormField } from "components/common"; import { SendTestEmailModal } from "./test-email-modal"; // types -import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types"; type IInstanceEmailForm = { config: IFormattedInstanceConfiguration; diff --git a/admin/app/email/layout.tsx b/admin/app/email/layout.tsx index ce1164ead..5eb8af8ee 100644 --- a/admin/app/email/layout.tsx +++ b/admin/app/email/layout.tsx @@ -2,7 +2,7 @@ import { ReactNode } from "react"; // layouts -import { AdminLayout } from "@/layouts"; +import { AdminLayout } from "@/layouts/admin-layout"; // lib import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; diff --git a/admin/app/email/page.tsx b/admin/app/email/page.tsx index 6ffebc904..51566e5f2 100644 --- a/admin/app/email/page.tsx +++ b/admin/app/email/page.tsx @@ -1,13 +1,13 @@ "use client"; -import useSWR from "swr"; import { observer } from "mobx-react-lite"; +import useSWR from "swr"; import { Loader } from "@plane/ui"; // components import { PageHeader } from "@/components/core"; +import { useInstance } from "@/hooks/store"; import { InstanceEmailForm } from "./components"; // hooks -import { useInstance } from "@/hooks/store"; const InstanceEmailPage = observer(() => { // store diff --git a/admin/app/error.tsx b/admin/app/error.tsx new file mode 100644 index 000000000..76794e04a --- /dev/null +++ b/admin/app/error.tsx @@ -0,0 +1,9 @@ +"use client"; + +export default function RootErrorPage() { + return ( +
+

Something went wrong.

+
+ ); +} diff --git a/admin/app/general/components/index.ts b/admin/app/general/components/index.ts deleted file mode 100644 index a144f8d63..000000000 --- a/admin/app/general/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./general-config-form"; \ No newline at end of file diff --git a/admin/app/general/components/general-config-form.tsx b/admin/app/general/form.tsx similarity index 94% rename from admin/app/general/components/general-config-form.tsx rename to admin/app/general/form.tsx index 5e360e048..09079028d 100644 --- a/admin/app/general/components/general-config-form.tsx +++ b/admin/app/general/form.tsx @@ -1,10 +1,14 @@ +"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"; @@ -13,7 +17,7 @@ export interface IGeneralConfigurationForm { instanceAdmins: IInstanceAdmin[]; } -export const GeneralConfigurationForm: FC = (props) => { +export const GeneralConfigurationForm: FC = observer((props) => { const { instance, instanceAdmins } = props; // hooks const { updateInstanceInfo } = useInstance(); @@ -24,8 +28,8 @@ export const GeneralConfigurationForm: FC = (props) = formState: { errors, isSubmitting }, } = useForm>({ defaultValues: { - instance_name: instance.instance_name, - is_telemetry_enabled: instance.is_telemetry_enabled, + instance_name: instance?.instance_name, + is_telemetry_enabled: instance?.is_telemetry_enabled, }, }); @@ -133,4 +137,4 @@ export const GeneralConfigurationForm: FC = (props) =
); -}; +}); diff --git a/admin/app/general/layout.tsx b/admin/app/general/layout.tsx index 1761f9689..371264e92 100644 --- a/admin/app/general/layout.tsx +++ b/admin/app/general/layout.tsx @@ -1,21 +1,12 @@ -"use client"; - import { ReactNode } from "react"; -// layouts -import { AdminLayout } from "@/layouts"; -// lib -import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; +import { Metadata } from "next"; +// components +import { AdminLayout } from "@/layouts/admin-layout"; -interface GeneralLayoutProps { - children: ReactNode; +export const metadata: Metadata = { + title: "General Settings - God Mode", +}; + +export default function GeneralLayout({ children }: { children: ReactNode }) { + return {children}; } - -const GeneralLayout = ({ children }: GeneralLayoutProps) => ( - - - {children} - - -); - -export default GeneralLayout; diff --git a/admin/app/general/page.tsx b/admin/app/general/page.tsx index accaf01d1..399482ea6 100644 --- a/admin/app/general/page.tsx +++ b/admin/app/general/page.tsx @@ -1,18 +1,15 @@ "use client"; - import { observer } from "mobx-react-lite"; -// components -import { PageHeader } from "@/components/core"; -import { GeneralConfigurationForm } from "./components"; // hooks import { useInstance } from "@/hooks/store"; +// components +import { GeneralConfigurationForm } from "./form"; -const GeneralPage = observer(() => { +function GeneralPage() { const { instance, instanceAdmins } = useInstance(); - + console.log("instance", instanceAdmins); return ( <> -
General settings
@@ -22,13 +19,13 @@ const GeneralPage = observer(() => {
- {instance?.instance && instanceAdmins && instanceAdmins?.length > 0 && ( - + {instance?.instance && instanceAdmins && ( + )}
); -}); +} -export default GeneralPage; +export default observer(GeneralPage); diff --git a/admin/app/image/components/index.ts b/admin/app/image/components/index.ts deleted file mode 100644 index ad9b60a10..000000000 --- a/admin/app/image/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./image-config-form"; \ No newline at end of file diff --git a/admin/app/image/components/image-config-form.tsx b/admin/app/image/form.tsx similarity index 97% rename from admin/app/image/components/image-config-form.tsx rename to admin/app/image/form.tsx index 1779468fa..a6fe2945b 100644 --- a/admin/app/image/components/image-config-form.tsx +++ b/admin/app/image/form.tsx @@ -1,9 +1,9 @@ import { FC } from "react"; import { useForm } from "react-hook-form"; -import { Button, TOAST_TYPE, setToast } from "@plane/ui"; import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types"; +import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // components -import { ControllerInput } from "components/common"; +import { ControllerInput } from "@/components/common"; // hooks import { useInstance } from "@/hooks/store"; diff --git a/admin/app/image/layout.tsx b/admin/app/image/layout.tsx index 4a42facfb..039c10202 100644 --- a/admin/app/image/layout.tsx +++ b/admin/app/image/layout.tsx @@ -1,21 +1,11 @@ -"use client"; - import { ReactNode } from "react"; // layouts -import { AdminLayout } from "@/layouts"; -// lib -import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; +import { AdminLayout } from "@/layouts/admin-layout"; interface ImageLayoutProps { children: ReactNode; } -const ImageLayout = ({ children }: ImageLayoutProps) => ( - - - {children} - - -); +const ImageLayout = ({ children }: ImageLayoutProps) => {children}; export default ImageLayout; diff --git a/admin/app/image/page.tsx b/admin/app/image/page.tsx index cbf4a8f4d..5c1b838be 100644 --- a/admin/app/image/page.tsx +++ b/admin/app/image/page.tsx @@ -1,13 +1,14 @@ "use client"; -import useSWR from "swr"; import { observer } from "mobx-react-lite"; +import useSWR from "swr"; import { Loader } from "@plane/ui"; // components import { PageHeader } from "@/components/core"; -import { InstanceImageConfigForm } from "./components"; // hooks import { useInstance } from "@/hooks/store"; +// local +import { InstanceImageConfigForm } from "./form"; const InstanceImagePage = observer(() => { // store diff --git a/admin/app/layout.tsx b/admin/app/layout.tsx index 3352cbfae..d2df31d59 100644 --- a/admin/app/layout.tsx +++ b/admin/app/layout.tsx @@ -1,46 +1,56 @@ -"use client"; - import { ReactNode } from "react"; -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"; +import { Metadata } from "next"; +// components +import { InstanceFailureView, InstanceNotReady } from "@/components/instance"; // helpers import { ASSET_PREFIX } from "@/helpers/common.helper"; +// lib +import { AppProvider } from "@/lib/app-providers"; // styles import "./globals.css"; +// services +import { InstanceService } from "@/services"; -interface RootLayoutProps { - children: ReactNode; +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 ( + + + + + + + + + + + {instanceDetails ? ( + <>{instanceDetails?.instance?.is_setup_done ? <>{children} : } + ) : ( + + )} + + + + ); } - -const RootLayout = ({ children, ...pageProps }: RootLayoutProps) => ( - - - {SITE_TITLE} - - - - - - - - - - - - - - - - - {children} - - - - -); - -export default RootLayout; diff --git a/admin/app/page.tsx b/admin/app/page.tsx index 05e9e8237..c7e6b975e 100644 --- a/admin/app/page.tsx +++ b/admin/app/page.tsx @@ -1,26 +1,11 @@ -"use client"; - -// 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"; +// layouts +import { DefaultLayout } from "@/layouts/default-layout"; -const LoginPage = () => ( - <> - - - - - - - - - -); - -export default LoginPage; +export default async function LoginPage() { + return ( + + + + ); +} diff --git a/admin/app/setup/components/sign-up-form.tsx b/admin/app/setup/components/sign-up-form.tsx index 0058b799e..1a117620d 100644 --- a/admin/app/setup/components/sign-up-form.tsx +++ b/admin/app/setup/components/sign-up-form.tsx @@ -2,17 +2,17 @@ import { FC, useEffect, useMemo, useState } from "react"; import { useSearchParams } from "next/navigation"; -// services -import { AuthService } from "@/services/auth.service"; +// icons +import { Eye, EyeOff } from "lucide-react"; // ui import { Button, Checkbox, Input, Spinner } from "@plane/ui"; // components -import { Banner, PasswordStrengthMeter } from "components/common"; -// icons -import { Eye, EyeOff } from "lucide-react"; +import { Banner, PasswordStrengthMeter } from "@/components/common"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; import { getPasswordStrength } from "@/helpers/password.helper"; +// services +import { AuthService } from "@/services/auth.service"; // service initialization const authService = new AuthService(); @@ -154,7 +154,7 @@ export const InstanceSignUpForm: FC = (props) => { First name * {
{ Email * {
{ diff --git a/admin/app/setup/layout.tsx b/admin/app/setup/layout.tsx index 07f42cd71..ba889b7ae 100644 --- a/admin/app/setup/layout.tsx +++ b/admin/app/setup/layout.tsx @@ -1,19 +1,23 @@ "use client"; import { ReactNode } from "react"; -// lib -import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; // helpers import { EAuthenticationPageType, EInstancePageType } from "@/helpers"; +// lib +import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers"; interface SetupLayoutProps { children: ReactNode; + params: any; } -const SetupLayout = ({ children }: SetupLayoutProps) => ( - - {children} - -); - -export default SetupLayout; +export default function SetupLayout(props: SetupLayoutProps) { + const { children, params } = props; + const { error_code } = params; + console.log("error_code", error_code); + return ( + + {children} + + ); +} diff --git a/admin/app/setup/page.tsx b/admin/app/setup/page.tsx index 641155c85..1ef22b1f2 100644 --- a/admin/app/setup/page.tsx +++ b/admin/app/setup/page.tsx @@ -1,16 +1,19 @@ +import { Metadata } from "next"; // layouts -import { DefaultLayout } from "@/layouts"; +import { DefaultLayout } from "@/layouts/default-layout"; // components -import { PageHeader } from "@/components/core"; import { InstanceSignUpForm } from "./components"; -const SetupPage = () => ( - <> - - - - - -); +export const metadata: Metadata = { + title: "Setup - God Mode", +}; -export default SetupPage; +export default function SetupPage() { + return ( + <> + + + + + ); +} diff --git a/admin/components/admin-sidebar/help-section.tsx b/admin/components/admin-sidebar/help-section.tsx index 84e28c67a..371bb49d8 100644 --- a/admin/components/admin-sidebar/help-section.tsx +++ b/admin/components/admin-sidebar/help-section.tsx @@ -1,16 +1,16 @@ "use client"; import { FC, useState, useRef } from "react"; -import Link from "next/link"; import { observer } from "mobx-react-lite"; -import { Transition } from "@headlessui/react"; +import Link from "next/link"; import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react"; +import { Transition } from "@headlessui/react"; import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui"; // hooks +import { WEB_BASE_URL } from "@/helpers/common.helper"; import { useTheme } from "@/hooks/store"; // assets import packageJson from "package.json"; -import { WEB_BASE_URL } from "@/helpers/common.helper"; const helpOptions = [ { diff --git a/admin/components/admin-sidebar/root.tsx b/admin/components/admin-sidebar/root.tsx index 654769924..ff94bf228 100644 --- a/admin/components/admin-sidebar/root.tsx +++ b/admin/components/admin-sidebar/root.tsx @@ -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 {} diff --git a/admin/components/admin-sidebar/sidebar-dropdown.tsx b/admin/components/admin-sidebar/sidebar-dropdown.tsx index f248f852f..f7de3f277 100644 --- a/admin/components/admin-sidebar/sidebar-dropdown.tsx +++ b/admin/components/admin-sidebar/sidebar-dropdown.tsx @@ -1,15 +1,15 @@ "use client"; import { Fragment, useEffect, useState } from "react"; -import { useTheme as useNextTheme } from "next-themes"; import { observer } from "mobx-react-lite"; +import { useTheme as useNextTheme } from "next-themes"; import { LogOut, UserCog2, Palette } from "lucide-react"; import { Menu, Transition } from "@headlessui/react"; import { Avatar } from "@plane/ui"; // hooks +import { 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"; diff --git a/admin/components/admin-sidebar/sidebar-menu-hamburger-toogle.tsx b/admin/components/admin-sidebar/sidebar-menu-hamburger-toogle.tsx index d6ed65541..2e8539488 100644 --- a/admin/components/admin-sidebar/sidebar-menu-hamburger-toogle.tsx +++ b/admin/components/admin-sidebar/sidebar-menu-hamburger-toogle.tsx @@ -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(); diff --git a/admin/components/admin-sidebar/sidebar-menu.tsx b/admin/components/admin-sidebar/sidebar-menu.tsx index dfb410051..f7c146fa2 100644 --- a/admin/components/admin-sidebar/sidebar-menu.tsx +++ b/admin/components/admin-sidebar/sidebar-menu.tsx @@ -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 = [ { diff --git a/admin/components/auth-header.tsx b/admin/components/auth-header.tsx index 21871aed4..4becf928f 100644 --- a/admin/components/auth-header.tsx +++ b/admin/components/auth-header.tsx @@ -1,16 +1,16 @@ "use client"; import { FC } from "react"; +import { observer } from "mobx-react-lite"; import { usePathname } from "next/navigation"; // mobx -import { observer } from "mobx-react-lite"; // ui import { Settings } from "lucide-react"; // icons import { Breadcrumbs } from "@plane/ui"; // components -import { BreadcrumbLink } from "components/common"; import { SidebarHamburgerToggle } from "@/components/admin-sidebar"; +import { BreadcrumbLink } from "components/common"; export const InstanceHeader: FC = observer(() => { const pathName = usePathname(); diff --git a/admin/components/common/controller-input.tsx b/admin/components/common/controller-input.tsx index d47fe43f9..8f2265954 100644 --- a/admin/components/common/controller-input.tsx +++ b/admin/components/common/controller-input.tsx @@ -3,9 +3,9 @@ import React, { useState } from "react"; import { Controller, Control } from "react-hook-form"; // ui +import { Eye, EyeOff } from "lucide-react"; import { Input } from "@plane/ui"; // icons -import { Eye, EyeOff } from "lucide-react"; // helpers import { cn } from "@/helpers/common.helper"; @@ -62,6 +62,7 @@ export const ControllerInput: React.FC = (props) => { {type === "password" && (showPassword ? ( ) : ( -

{description}

+
{description}
); }; diff --git a/admin/components/common/password-strength-meter.tsx b/admin/components/common/password-strength-meter.tsx index fabb186f9..5cdba30b7 100644 --- a/admin/components/common/password-strength-meter.tsx +++ b/admin/components/common/password-strength-meter.tsx @@ -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; diff --git a/admin/components/instance/index.ts b/admin/components/instance/index.ts index 373ba7057..1f52843a0 100644 --- a/admin/components/instance/index.ts +++ b/admin/components/instance/index.ts @@ -1 +1,2 @@ export * from "./instance-not-ready"; +export * from "./instance-failure-view"; diff --git a/admin/components/instance/instance-failure-view.tsx b/admin/components/instance/instance-failure-view.tsx new file mode 100644 index 000000000..b86750031 --- /dev/null +++ b/admin/components/instance/instance-failure-view.tsx @@ -0,0 +1,42 @@ +"use client"; +import { FC } from "react"; +import Image from "next/image"; +import { useTheme } from "next-themes"; +import { Button } from "@plane/ui"; +// assets +import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg"; +import InstanceFailureImage from "@/public/instance/instance-failure.svg"; + +type InstanceFailureViewProps = { + // mutate: () => void; +}; + +export const InstanceFailureView: FC = () => { + const { resolvedTheme } = useTheme(); + + const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage; + + const handleRetry = () => { + window.location.reload(); + }; + + return ( +
+
+
+ Plane Logo +

Unable to fetch instance details.

+

+ We were unable to fetch the details of the instance.
+ Fret not, it might just be a connectivity issue. +

+
+
+ +
+
+
+ ); +}; diff --git a/admin/components/instance/instance-not-ready.tsx b/admin/components/instance/instance-not-ready.tsx index d3df5bd66..874013f52 100644 --- a/admin/components/instance/instance-not-ready.tsx +++ b/admin/components/instance/instance-not-ready.tsx @@ -1,8 +1,8 @@ "use client"; import { FC } from "react"; -import Link from "next/link"; import Image from "next/image"; +import Link from "next/link"; import { Button } from "@plane/ui"; // assets import PlaneTakeOffImage from "@/public/images/plane-takeoff.png"; diff --git a/admin/components/login/sign-in-form.tsx b/admin/components/login/sign-in-form.tsx index 12df47edf..b68e78197 100644 --- a/admin/components/login/sign-in-form.tsx +++ b/admin/components/login/sign-in-form.tsx @@ -3,15 +3,15 @@ import { FC, useEffect, useMemo, useState } from "react"; import { useSearchParams } from "next/navigation"; // services -import { AuthService } from "@/services/auth.service"; -// ui +import { Eye, EyeOff } from "lucide-react"; import { Button, Input, Spinner } from "@plane/ui"; // components -import { Banner } from "components/common"; -// icons -import { Eye, EyeOff } from "lucide-react"; +import { Banner } from "@/components/common"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; +import { AuthService } from "@/services/auth.service"; +// ui +// icons // service initialization const authService = new AuthService(); @@ -57,6 +57,8 @@ export const InstanceSignInForm: FC = (props) => { const handleFormChange = (key: keyof TFormData, value: string | boolean) => setFormData((prev) => ({ ...prev, [key]: value })); + console.log("csrfToken", csrfToken); + useEffect(() => { if (csrfToken === undefined) authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); @@ -119,7 +121,7 @@ export const InstanceSignInForm: FC = (props) => { Email * {
{ diff --git a/admin/hooks/store/use-theme.tsx b/admin/hooks/store/use-theme.tsx index dc4f9dbf8..95d2aa05e 100644 --- a/admin/hooks/store/use-theme.tsx +++ b/admin/hooks/store/use-theme.tsx @@ -1,6 +1,6 @@ import { useContext } from "react"; // store -import { StoreContext } from "@/lib/store-context"; +import { StoreContext } from "@/lib/app-providers"; import { IThemeStore } from "@/store/theme.store"; export const useTheme = (): IThemeStore => { diff --git a/admin/hooks/store/use-user.tsx b/admin/hooks/store/use-user.tsx index d1e114ae4..c8cb45250 100644 --- a/admin/hooks/store/use-user.tsx +++ b/admin/hooks/store/use-user.tsx @@ -1,6 +1,6 @@ import { useContext } from "react"; // store -import { StoreContext } from "@/lib/store-context"; +import { StoreContext } from "@/lib/app-providers"; import { IUserStore } from "@/store/user.store"; export const useUser = (): IUserStore => { diff --git a/admin/layouts/admin-layout.tsx b/admin/layouts/admin-layout.tsx index 131fb7fcd..041a5b8bf 100644 --- a/admin/layouts/admin-layout.tsx +++ b/admin/layouts/admin-layout.tsx @@ -1,15 +1,48 @@ -import { FC, ReactNode } from "react"; +"use client"; +import { FC, ReactNode, useEffect } from "react"; +import { observer } from "mobx-react-lite"; +import { useRouter } from "next/navigation"; +import useSWR from "swr"; +// ui +import { Spinner } from "@plane/ui"; // components import { InstanceSidebar } from "@/components/admin-sidebar"; import { InstanceHeader } from "@/components/auth-header"; import { NewUserPopup } from "@/components/new-user-popup"; +// hooks +import { useInstance, useUser } from "@/hooks/store"; type TAdminLayout = { children: ReactNode; }; -export const AdminLayout: FC = (props) => { +export const AdminLayout: FC = observer((props) => { const { children } = props; + // router + const router = useRouter(); + // hooks + const { fetchInstanceAdmins } = useInstance(); + const { fetchCurrentUser, isUserLoggedIn } = useUser(); + + useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins()); + + useSWR("CURRENT_USER", () => fetchCurrentUser(), { + shouldRetryOnError: false, + }); + + useEffect(() => { + if (isUserLoggedIn === false) { + router.push("/"); + } + }, [router, isUserLoggedIn]); + + if (isUserLoggedIn === undefined) { + return ( +
+ +
+ ); + } return (
@@ -21,4 +54,4 @@ export const AdminLayout: FC = (props) => {
); -}; +}); diff --git a/admin/layouts/default-layout.tsx b/admin/layouts/default-layout.tsx index e0952e994..26bab1221 100644 --- a/admin/layouts/default-layout.tsx +++ b/admin/layouts/default-layout.tsx @@ -17,6 +17,7 @@ export const DefaultLayout: FC = (props) => { const { children, withoutBackground = false } = props; // hooks const { resolvedTheme } = useTheme(); + const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern; return (
@@ -29,11 +30,7 @@ export const DefaultLayout: FC = (props) => {
{!withoutBackground && (
- Plane background pattern + Plane background pattern
)}
{children}
diff --git a/admin/layouts/index.ts b/admin/layouts/index.ts deleted file mode 100644 index 5e4a7c023..000000000 --- a/admin/layouts/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./default-layout"; -export * from "./admin-layout"; diff --git a/admin/lib/app-providers.tsx b/admin/lib/app-providers.tsx new file mode 100644 index 000000000..28bfdd08b --- /dev/null +++ b/admin/lib/app-providers.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { ReactNode, createContext } from "react"; +import { ThemeProvider } from "next-themes"; +// ui +import { AppWrapper } from "@/lib/wrappers"; +// store +import { RootStore } from "@/store/root.store"; + +let rootStore = new RootStore(); + +export const StoreContext = createContext(rootStore); + +function initializeStore(initialData = {}) { + const singletonRootStore = rootStore ?? new RootStore(); + // If your page has Next.js data fetching methods that use a Mobx store, it will + // get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details + if (initialData) { + console.log("initialState", initialData); + singletonRootStore.hydrate(initialData); + } + // For SSG and SSR always create a new store + if (typeof window === "undefined") return singletonRootStore; + // Create the store once in the client + if (!rootStore) rootStore = singletonRootStore; + return singletonRootStore; +} + +export type AppProviderProps = { + children: ReactNode; + initialState: any; +}; + +export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => { + const store = initializeStore(initialState); + + return ( + + + {children} + + + ); +}; diff --git a/admin/lib/store-context.tsx b/admin/lib/store-context.tsx deleted file mode 100644 index 8893f1a78..000000000 --- a/admin/lib/store-context.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import { ReactElement, createContext } from "react"; -// mobx store -import { RootStore } from "@/store/root-store"; - -export let rootStore = new RootStore(); - -export const StoreContext = createContext(rootStore); - -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 {children}; -}; diff --git a/admin/lib/wrappers/app-wrapper.tsx b/admin/lib/wrappers/app-wrapper.tsx index aa6e26330..daf030a5c 100644 --- a/admin/lib/wrappers/app-wrapper.tsx +++ b/admin/lib/wrappers/app-wrapper.tsx @@ -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"; +import { resolveGeneralTheme } from "@/helpers/common.helper"; +// hooks +import { useTheme, useUser } from "@/hooks/store"; interface IAppWrapper { children: ReactNode; diff --git a/admin/lib/wrappers/auth-wrapper.tsx b/admin/lib/wrappers/auth-wrapper.tsx index 00f947047..c471b3a23 100644 --- a/admin/lib/wrappers/auth-wrapper.tsx +++ b/admin/lib/wrappers/auth-wrapper.tsx @@ -1,14 +1,14 @@ "use client"; import { FC, ReactNode } from "react"; -import { useRouter } from "next/navigation"; import { observer } from "mobx-react-lite"; +import { useRouter } from "next/navigation"; import useSWR from "swr"; import { Spinner } from "@plane/ui"; // hooks +import { EAuthenticationPageType } from "@/helpers"; import { useInstance, useUser } from "@/hooks/store"; // helpers -import { EAuthenticationPageType } from "@/helpers"; export interface IAuthWrapper { children: ReactNode; diff --git a/admin/lib/wrappers/instance-wrapper.tsx b/admin/lib/wrappers/instance-wrapper.tsx index f86adfdce..bfc5cc289 100644 --- a/admin/lib/wrappers/instance-wrapper.tsx +++ b/admin/lib/wrappers/instance-wrapper.tsx @@ -1,19 +1,19 @@ "use client"; import { FC, ReactNode } from "react"; -import { redirect, useSearchParams } from "next/navigation"; import { observer } from "mobx-react-lite"; +import { redirect, useSearchParams } from "next/navigation"; import useSWR from "swr"; import { Spinner } from "@plane/ui"; -// layouts -import { DefaultLayout } from "@/layouts"; // components +import { EmptyState } from "@/components/common"; import { InstanceNotReady } from "@/components/instance"; -// hooks -import { useInstance } from "@/hooks/store"; // helpers import { EInstancePageType } from "@/helpers"; -import { EmptyState } from "@/components/common"; +// hooks +import { useInstance } from "@/hooks/store"; +// layouts +import { DefaultLayout } from "@/layouts/default-layout"; type TInstanceWrapper = { children: ReactNode; diff --git a/admin/public/instance/instance-failure-dark.svg b/admin/public/instance/instance-failure-dark.svg new file mode 100644 index 000000000..58d691705 --- /dev/null +++ b/admin/public/instance/instance-failure-dark.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/admin/public/instance/instance-failure.svg b/admin/public/instance/instance-failure.svg new file mode 100644 index 000000000..a59862283 --- /dev/null +++ b/admin/public/instance/instance-failure.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/admin/public/instance/plane-instance-not-ready.webp b/admin/public/instance/plane-instance-not-ready.webp new file mode 100644 index 000000000..a0efca52c Binary files /dev/null and b/admin/public/instance/plane-instance-not-ready.webp differ diff --git a/admin/public/instance/plane-takeoff.png b/admin/public/instance/plane-takeoff.png new file mode 100644 index 000000000..417ff8299 Binary files /dev/null and b/admin/public/instance/plane-takeoff.png differ diff --git a/admin/services/api.service.ts b/admin/services/api.service.ts index 344cd4f54..fa45c10b7 100644 --- a/admin/services/api.service.ts +++ b/admin/services/api.service.ts @@ -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(url: string, params = {}): Promise> { diff --git a/admin/services/auth.service.ts b/admin/services/auth.service.ts index 6e173140e..ef7b7b151 100644 --- a/admin/services/auth.service.ts +++ b/admin/services/auth.service.ts @@ -1,7 +1,7 @@ -// services -import { APIService } from "services/api.service"; // helpers import { API_BASE_URL } from "helpers/common.helper"; +// services +import { APIService } from "services/api.service"; type TCsrfTokenResponse = { csrf_token: string; diff --git a/admin/services/instance.service.ts b/admin/services/instance.service.ts index 109b52e44..e995ee821 100644 --- a/admin/services/instance.service.ts +++ b/admin/services/instance.service.ts @@ -13,6 +13,7 @@ export class InstanceService extends APIService { return this.get("/api/instances/") .then((response) => response.data) .catch((error) => { + console.log("error", error); throw error; }); } diff --git a/admin/services/user.service.ts b/admin/services/user.service.ts index 9209ec460..bef384daf 100644 --- a/admin/services/user.service.ts +++ b/admin/services/user.service.ts @@ -1,15 +1,25 @@ +// helpers +import { API_BASE_URL } from "helpers/common.helper"; // services import { APIService } from "services/api.service"; // types import type { IUser } from "@plane/types"; -// helpers -import { API_BASE_URL } from "helpers/common.helper"; + +interface IUserSession extends IUser { + isAuthenticated: boolean; +} export class UserService extends APIService { constructor() { super(API_BASE_URL); } + async authCheck(): Promise { + return this.get("/api/instances/admins/me/") + .then((response) => ({ ...response?.data, isAuthenticated: true })) + .catch(() => ({ isAuthenticated: false })); + } + async currentUser(): Promise { return this.get("/api/instances/admins/me/") .then((response) => response?.data) diff --git a/admin/store/instance.store.ts b/admin/store/instance.store.ts index 70b505ad0..e168b15b6 100644 --- a/admin/store/instance.store.ts +++ b/admin/store/instance.store.ts @@ -1,12 +1,12 @@ -import { observable, action, computed, makeObservable, runInAction } from "mobx"; import set from "lodash/set"; +import { observable, action, computed, makeObservable, runInAction } from "mobx"; import { IInstance, IInstanceAdmin, IInstanceConfiguration, IFormattedInstanceConfiguration } from "@plane/types"; // helpers import { EInstanceStatus, TInstanceStatus } from "@/helpers"; // services import { InstanceService } from "@/services/instance.service"; // root store -import { RootStore } from "@/store/root-store"; +import { RootStore } from "@/store/root.store"; export interface IInstanceStore { // issues @@ -18,11 +18,12 @@ export interface IInstanceStore { // computed formattedConfig: IFormattedInstanceConfiguration | undefined; // action + hydrate: (data: any) => void; fetchInstanceInfo: () => Promise; updateInstanceInfo: (data: Partial) => Promise; fetchInstanceAdmins: () => Promise; fetchInstanceConfigurations: () => Promise; - updateInstanceConfigurations: (data: Partial) => Promise; + updateInstanceConfigurations: (data: Partial) => Promise; } export class InstanceStore implements IInstanceStore { @@ -45,6 +46,7 @@ export class InstanceStore implements IInstanceStore { // computed formattedConfig: computed, // actions + hydrate: action, fetchInstanceInfo: action, fetchInstanceAdmins: action, updateInstanceInfo: action, @@ -55,6 +57,10 @@ export class InstanceStore implements IInstanceStore { this.instanceService = new InstanceService(); } + hydrate = (data: any) => { + if (data) this.instance = data; + }; + /** * computed value for instance configurations data for forms. * @returns configurations in the form of {key, value} pair. @@ -148,13 +154,15 @@ export class InstanceStore implements IInstanceStore { */ updateInstanceConfigurations = async (data: Partial) => { try { - await this.instanceService.updateInstanceConfigurations(data).then((response) => { - runInAction(() => { - this.instanceConfigurations = this.instanceConfigurations - ? [...this.instanceConfigurations, ...response] - : response; + const response = await this.instanceService.updateInstanceConfigurations(data); + runInAction(() => { + this.instanceConfigurations = this.instanceConfigurations?.map((config) => { + const item = response.find((item) => item.key === config.key); + if (item) return item; + return config; }); }); + return response; } catch (error) { console.error("Error updating the instance configurations"); throw error; diff --git a/admin/store/root-store.ts b/admin/store/root.store.ts similarity index 81% rename from admin/store/root-store.ts rename to admin/store/root.store.ts index c05cce37f..553a22200 100644 --- a/admin/store/root-store.ts +++ b/admin/store/root.store.ts @@ -1,7 +1,7 @@ import { enableStaticRendering } from "mobx-react-lite"; // stores -import { IThemeStore, ThemeStore } from "./theme.store"; import { IInstanceStore, InstanceStore } from "./instance.store"; +import { IThemeStore, ThemeStore } from "./theme.store"; import { IUserStore, UserStore } from "./user.store"; enableStaticRendering(typeof window === "undefined"); @@ -17,9 +17,14 @@ 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); diff --git a/admin/store/theme.store.ts b/admin/store/theme.store.ts index 886507922..a3f3b3d5a 100644 --- a/admin/store/theme.store.ts +++ b/admin/store/theme.store.ts @@ -1,6 +1,6 @@ import { action, observable, makeObservable } from "mobx"; // root store -import { RootStore } from "@/store/root-store"; +import { RootStore } from "@/store/root.store"; type TTheme = "dark" | "light"; export interface IThemeStore { @@ -9,6 +9,7 @@ export interface IThemeStore { theme: string | undefined; isSidebarCollapsed: boolean | undefined; // actions + hydrate: (data: any) => void; toggleNewUserPopup: () => void; toggleSidebar: (collapsed: boolean) => void; setTheme: (currentTheme: TTheme) => void; @@ -33,6 +34,10 @@ export class ThemeStore implements IThemeStore { }); } + hydrate = (data: any) => { + if (data) this.theme = data; + }; + /** * @description Toggle the new user popup modal */ diff --git a/admin/store/user.store.ts b/admin/store/user.store.ts index 10b5eab81..271c6be34 100644 --- a/admin/store/user.store.ts +++ b/admin/store/user.store.ts @@ -3,10 +3,10 @@ import { IUser } from "@plane/types"; // helpers import { EUserStatus, TUserStatus } from "@/helpers"; // services +import { AuthService } from "@/services"; import { UserService } from "@/services/user.service"; // root store -import { RootStore } from "@/store/root-store"; -import { AuthService } from "@/services"; +import { RootStore } from "@/store/root.store"; export interface IUserStore { // observables @@ -15,6 +15,7 @@ export interface IUserStore { isUserLoggedIn: boolean | undefined; currentUser: IUser | undefined; // fetch actions + hydrate: (data: any) => void; fetchCurrentUser: () => Promise; reset: () => void; signOut: () => void; @@ -46,6 +47,10 @@ export class UserStore implements IUserStore { this.authService = new AuthService(); } + hydrate = (data: any) => { + if (data) this.currentUser = data; + }; + /** * @description Fetches the current user * @returns Promise diff --git a/apiserver/plane/license/api/views/admin.py b/apiserver/plane/license/api/views/admin.py index 0abac6b14..6f354d286 100644 --- a/apiserver/plane/license/api/views/admin.py +++ b/apiserver/plane/license/api/views/admin.py @@ -243,6 +243,7 @@ class InstanceAdminSignUpEndpoint(View): ) # Make the setup flag True instance.is_setup_done = True + instance.instance_name = company_name instance.is_telemetry_enabled = is_telemetry_enabled instance.save() diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py index e8b9db447..b175e4c83 100644 --- a/apiserver/plane/settings/local.py +++ b/apiserver/plane/settings/local.py @@ -13,7 +13,9 @@ MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) # noqa DEBUG_TOOLBAR_PATCH_SETTINGS = False # Only show emails in console don't send it to smtp -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +EMAIL_BACKEND = os.environ.get( + "EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend" +) CACHES = { "default": { diff --git a/apiserver/plane/utils/exception_logger.py b/apiserver/plane/utils/exception_logger.py index f7bb50de2..0938f054b 100644 --- a/apiserver/plane/utils/exception_logger.py +++ b/apiserver/plane/utils/exception_logger.py @@ -6,6 +6,7 @@ from sentry_sdk import capture_exception def log_exception(e): + print(e) # Log the error logger = logging.getLogger("plane") logger.error(e) diff --git a/space/app/layout.tsx b/space/app/layout.tsx index b5501957e..78b4a698b 100644 --- a/space/app/layout.tsx +++ b/space/app/layout.tsx @@ -23,11 +23,11 @@ export default async function RootLayout({ children }: { children: React.ReactNo return ( - - - - - + + + + + {children} diff --git a/space/services/api.service.ts b/space/services/api.service.ts index a5fe3e93d..28be5a131 100644 --- a/space/services/api.service.ts +++ b/space/services/api.service.ts @@ -3,7 +3,7 @@ import axios, { AxiosInstance } from "axios"; // store // import { rootStore } from "@/lib/store-context"; -abstract class APIService { +export abstract class APIService { protected baseURL: string; private axiosInstance: AxiosInstance; @@ -52,5 +52,3 @@ abstract class APIService { return this.axiosInstance(config); } } - -export default APIService; diff --git a/space/services/instance.service.ts b/space/services/instance.service.ts index fb7ab5896..7744f1f65 100644 --- a/space/services/instance.service.ts +++ b/space/services/instance.service.ts @@ -3,7 +3,7 @@ import type { IInstance } from "@plane/types"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; // services -import APIService from "@/services/api.service"; +import { APIService } from "@/services/api.service"; export class InstanceService extends APIService { constructor() { diff --git a/web/helpers/common.helper.ts b/web/helpers/common.helper.ts index 2f4814194..cc173b497 100644 --- a/web/helpers/common.helper.ts +++ b/web/helpers/common.helper.ts @@ -6,7 +6,7 @@ export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || ""; export const ADMIN_BASE_URL = process.env.NEXT_PUBLIC_ADMIN_BASE_URL || ""; export const ADMIN_BASE_PATH = process.env.NEXT_PUBLIC_ADMIN_BASE_PATH || ""; -export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || ""; +export const SPACE_BASE_URL = process.env.NEXT_PUBLIC_SPACE_BASE_URL || window.location.origin; export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || ""; export const debounce = (func: any, wait: number, immediate: boolean = false) => {