Compare commits

..

1 Commits

Author SHA1 Message Date
pablohashescobar e5e1619c98 dev: fix space authentication 2024-05-10 18:10:11 +05:30
581 changed files with 8052 additions and 14505 deletions
-59
View File
@@ -1,59 +0,0 @@
/**
* Adds three new lint plugins over the existing configuration:
* This is used to lint staged files only.
* We should remove this file once the entire codebase follows these rules.
*/
module.exports = {
root: true,
extends: [
"custom",
],
parser: "@typescript-eslint/parser",
settings: {
"import/resolver": {
typescript: {},
node: {
moduleDirectory: ["node_modules", "."],
},
},
},
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,
},
},
],
},
};
+16 -16
View File
@@ -14,10 +14,10 @@ jobs:
space_changed: ${{ steps.changed-files.outputs.space_any_changed }}
web_changed: ${{ steps.changed-files.outputs.web_any_changed }}
steps:
- uses: actions/checkout@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 -1
View File
@@ -64,6 +64,6 @@ jobs:
echo "Pull Request already exists: $PR_EXISTS"
else
echo "Creating new pull request"
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: community changes" --body "")
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: merge conflicts need to be resolved" --body "")
echo "Pull Request created: $PR_URL"
fi
+1 -4
View File
@@ -81,7 +81,4 @@ tmp/
## packages
dist
.temp/
deploy/selfhost/plane-app/
## Storybook
*storybook.log
output.css
deploy/selfhost/plane-app/
View File
-3
View File
@@ -1,3 +0,0 @@
{
"*.{ts,tsx,js,jsx}": ["eslint -c ./.eslintrc-staged.js", "prettier --check"]
}
+1 -1
View File
@@ -82,7 +82,7 @@ COPY apiserver/templates templates/
RUN apk --no-cache add "bash~=5.2"
COPY apiserver/bin ./bin/
RUN chmod +x ./bin/*
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod -R 777 /code
# Expose container port and run entry point script
+1 -39
View File
@@ -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: {}
}
+10 -22
View File
@@ -29,21 +29,15 @@ COPY turbo.json turbo.json
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED 1
ENV TURBO_TELEMETRY_DISABLED 1
@@ -65,21 +59,15 @@ COPY --from=installer /app/admin/public ./admin/public
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED 1
ENV TURBO_TELEMETRY_DISABLED 1
@@ -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";
+1
View File
@@ -0,0 +1 @@
export * from "./ai-config-form";
+18 -8
View File
@@ -1,11 +1,21 @@
"use client";
import { ReactNode } from "react";
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
export const metadata: Metadata = {
title: "AI Settings - God Mode",
};
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;
+2 -3
View File
@@ -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 -3
View File
@@ -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;
@@ -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";
@@ -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;
+5 -6
View File
@@ -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
@@ -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";
@@ -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;
+3 -4
View File
@@ -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
+18 -8
View File
@@ -1,11 +1,21 @@
"use client";
import { ReactNode } from "react";
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
export const metadata: Metadata = {
title: "Authentication Settings - God Mode",
};
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;
+7 -12
View File
@@ -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;
@@ -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;
+2
View File
@@ -0,0 +1,2 @@
export * from "./email-config-form";
export * from "./test-email-modal";
@@ -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;
+13 -7
View File
@@ -1,15 +1,21 @@
"use client";
import { ReactNode } from "react";
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
interface EmailLayoutProps {
children: ReactNode;
}
export const metadata: Metadata = {
title: "Email Settings - God Mode",
};
const EmailLayout = ({ children }: EmailLayoutProps) => <AdminLayout>{children}</AdminLayout>;
const EmailLayout = ({ children }: EmailLayoutProps) => (
<InstanceWrapper>
<AuthWrapper>
<AdminLayout>{children}</AdminLayout>
</AuthWrapper>
</InstanceWrapper>
);
export default EmailLayout;
+2 -3
View File
@@ -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
-9
View File
@@ -1,9 +0,0 @@
"use client";
export default function RootErrorPage() {
return (
<div>
<p>Something went wrong.</p>
</div>
);
}
@@ -1,23 +1,19 @@
"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";
export interface IGeneralConfigurationForm {
instance: IInstance;
instance: IInstance["instance"];
instanceAdmins: IInstanceAdmin[];
}
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer((props) => {
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = (props) => {
const { instance, instanceAdmins } = props;
// hooks
const { updateInstanceInfo } = useInstance();
@@ -26,15 +22,15 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
handleSubmit,
control,
formState: { errors, isSubmitting },
} = useForm<Partial<IInstance>>({
} = 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,
},
});
const onSubmit = async (formData: Partial<IInstance>) => {
const payload: Partial<IInstance> = { ...formData };
const onSubmit = async (formData: Partial<IInstance["instance"]>) => {
const payload: Partial<IInstance["instance"]> = { ...formData };
console.log("payload", payload);
@@ -137,4 +133,4 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
</div>
</div>
);
});
};
+1
View File
@@ -0,0 +1 @@
export * from "./general-config-form";
+18 -8
View File
@@ -1,11 +1,21 @@
"use client";
import { ReactNode } from "react";
import { Metadata } from "next";
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;
+11 -8
View File
@@ -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", instance);
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 && instanceAdmins && (
<GeneralConfigurationForm 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;
+134 -52
View File
@@ -45,8 +45,8 @@
--color-primary-900: 13, 24, 51;
--color-background-100: 255, 255, 255; /* primary bg */
--color-background-90: 247, 247, 247; /* secondary bg */
--color-background-80: 232, 232, 232; /* tertiary bg */
--color-background-90: 250, 250, 250; /* secondary bg */
--color-background-80: 245, 245, 245; /* tertiary bg */
--color-text-100: 23, 23, 23; /* primary text */
--color-text-200: 58, 58, 58; /* secondary text */
@@ -60,39 +60,66 @@
--color-border-300: 212, 212, 212; /* strong border- 1 */
--color-border-400: 185, 185, 185; /* strong border- 2 */
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06), 0px 1px 2px 0px rgba(23, 23, 23, 0.06),
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06),
0px 1px 2px 0px rgba(23, 23, 23, 0.06),
0px 1px 2px 0px rgba(23, 23, 23, 0.14);
--color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16), 0px 2px 4px 0px rgba(16, 24, 40, 0.12),
--color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16),
0px 2px 4px 0px rgba(16, 24, 40, 0.12),
0px 1px 8px -1px rgba(16, 24, 40, 0.1);
--color-shadow-sm: 0px 1px 4px 0px rgba(0, 0, 0, 0.01), 0px 4px 8px 0px rgba(0, 0, 0, 0.02),
0px 1px 12px 0px rgba(0, 0, 0, 0.12);
--color-shadow-rg: 0px 3px 6px 0px rgba(0, 0, 0, 0.1), 0px 4px 4px 0px rgba(16, 24, 40, 0.08),
--color-shadow-sm: 0px 1px 4px 0px rgba(0, 0, 0, 0.01),
0px 4px 8px 0px rgba(0, 0, 0, 0.02), 0px 1px 12px 0px rgba(0, 0, 0, 0.12);
--color-shadow-rg: 0px 3px 6px 0px rgba(0, 0, 0, 0.1),
0px 4px 4px 0px rgba(16, 24, 40, 0.08),
0px 1px 12px 0px rgba(16, 24, 40, 0.04);
--color-shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 6px 12px 0px rgba(16, 24, 40, 0.12),
--color-shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.12),
0px 6px 12px 0px rgba(16, 24, 40, 0.12),
0px 1px 16px 0px rgba(16, 24, 40, 0.12);
--color-shadow-lg: 0px 6px 12px 0px rgba(0, 0, 0, 0.12), 0px 8px 16px 0px rgba(0, 0, 0, 0.12),
--color-shadow-lg: 0px 6px 12px 0px rgba(0, 0, 0, 0.12),
0px 8px 16px 0px rgba(0, 0, 0, 0.12),
0px 1px 24px 0px rgba(16, 24, 40, 0.12);
--color-shadow-xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.16), 0px 0px 24px 0px rgba(16, 24, 40, 0.16),
--color-shadow-xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.16),
0px 0px 24px 0px rgba(16, 24, 40, 0.16),
0px 0px 52px 0px rgba(16, 24, 40, 0.16);
--color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12), 0px 12px 24px 0px rgba(16, 24, 40, 0.12),
--color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12),
0px 12px 24px 0px rgba(16, 24, 40, 0.12),
0px 1px 32px 0px rgba(16, 24, 40, 0.12);
--color-shadow-3xl: 0px 12px 24px 0px rgba(0, 0, 0, 0.12), 0px 16px 32px 0px rgba(0, 0, 0, 0.12),
--color-shadow-3xl: 0px 12px 24px 0px rgba(0, 0, 0, 0.12),
0px 16px 32px 0px rgba(0, 0, 0, 0.12),
0px 1px 48px 0px rgba(16, 24, 40, 0.12);
--color-shadow-4xl: 0px 8px 40px 0px rgba(0, 0, 61, 0.05), 0px 12px 32px -16px rgba(0, 0, 0, 0.05);
--color-shadow-4xl: 0px 8px 40px 0px rgba(0, 0, 61, 0.05),
0px 12px 32px -16px rgba(0, 0, 0, 0.05);
--color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */
--color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */
--color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */
--color-sidebar-background-100: var(
--color-background-100
); /* primary sidebar bg */
--color-sidebar-background-90: var(
--color-background-90
); /* secondary sidebar bg */
--color-sidebar-background-80: var(
--color-background-80
); /* tertiary sidebar bg */
--color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */
--color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */
--color-sidebar-text-200: var(
--color-text-200
); /* secondary sidebar text */
--color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
--color-sidebar-text-400: var(
--color-text-400
); /* sidebar placeholder text */
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(--color-border-100); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(--color-border-100); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(--color-border-100); /* strong sidebar border- 2 */
--color-sidebar-border-100: var(
--color-border-100
); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(
--color-border-100
); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(
--color-border-100
); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(
--color-border-100
); /* strong sidebar border- 2 */
--color-sidebar-shadow-2xs: var(--color-shadow-2xs);
--color-sidebar-shadow-xs: var(--color-shadow-xs);
@@ -111,8 +138,8 @@
color-scheme: light !important;
--color-background-100: 255, 255, 255; /* primary bg */
--color-background-90: 247, 247, 247; /* secondary bg */
--color-background-80: 232, 232, 232; /* tertiary bg */
--color-background-90: 250, 250, 250; /* secondary bg */
--color-background-80: 245, 245, 245; /* tertiary bg */
}
[data-theme="light"] {
@@ -129,10 +156,26 @@
--color-border-400: 185, 185, 185; /* strong border- 2 */
/* onboarding colors */
--gradient-onboarding-100: linear-gradient(106deg, #f2f6ff 29.8%, #e1eaff 99.34%);
--gradient-onboarding-200: linear-gradient(129deg, rgba(255, 255, 255, 0) -22.23%, rgba(255, 255, 255, 0.8) 62.98%);
--gradient-onboarding-300: linear-gradient(164deg, #fff 4.25%, rgba(255, 255, 255, 0.06) 93.5%);
--gradient-onboarding-400: linear-gradient(129deg, rgba(255, 255, 255, 0) -22.23%, rgba(255, 255, 255, 0.8) 62.98%);
--gradient-onboarding-100: linear-gradient(
106deg,
#f2f6ff 29.8%,
#e1eaff 99.34%
);
--gradient-onboarding-200: linear-gradient(
129deg,
rgba(255, 255, 255, 0) -22.23%,
rgba(255, 255, 255, 0.8) 62.98%
);
--gradient-onboarding-300: linear-gradient(
164deg,
#fff 4.25%,
rgba(255, 255, 255, 0.06) 93.5%
);
--gradient-onboarding-400: linear-gradient(
129deg,
rgba(255, 255, 255, 0) -22.23%,
rgba(255, 255, 255, 0.8) 62.98%
);
--color-onboarding-text-100: 23, 23, 23;
--color-onboarding-text-200: 58, 58, 58;
@@ -190,19 +233,28 @@
[data-theme="dark-contrast"] {
color-scheme: dark !important;
--color-background-100: 25, 25, 25; /* primary bg */
--color-background-90: 32, 32, 32; /* secondary bg */
--color-background-80: 44, 44, 44; /* tertiary bg */
--color-background-100: 7, 7, 7; /* primary bg */
--color-background-90: 11, 11, 11; /* secondary bg */
--color-background-80: 23, 23, 23; /* tertiary bg */
--color-shadow-2xs: 0px 0px 1px 0px rgba(0, 0, 0, 0.15), 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
--color-shadow-xs: 0px 0px 2px 0px rgba(0, 0, 0, 0.2), 0px 2px 4px 0px rgba(0, 0, 0, 0.5);
--color-shadow-sm: 0px 0px 4px 0px rgba(0, 0, 0, 0.2), 0px 2px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-rg: 0px 0px 6px 0px rgba(0, 0, 0, 0.2), 0px 4px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-md: 0px 2px 8px 0px rgba(0, 0, 0, 0.2), 0px 4px 8px 0px rgba(0, 0, 0, 0.5);
--color-shadow-lg: 0px 4px 12px 0px rgba(0, 0, 0, 0.25), 0px 4px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25), 0px 6px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25), 0px 8px 12px 0px rgba(0, 0, 0, 0.6);
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3), 0px 12px 40px 0px rgba(0, 0, 0, 0.65);
--color-shadow-2xs: 0px 0px 1px 0px rgba(0, 0, 0, 0.15),
0px 1px 3px 0px rgba(0, 0, 0, 0.5);
--color-shadow-xs: 0px 0px 2px 0px rgba(0, 0, 0, 0.2),
0px 2px 4px 0px rgba(0, 0, 0, 0.5);
--color-shadow-sm: 0px 0px 4px 0px rgba(0, 0, 0, 0.2),
0px 2px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-rg: 0px 0px 6px 0px rgba(0, 0, 0, 0.2),
0px 4px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-md: 0px 2px 8px 0px rgba(0, 0, 0, 0.2),
0px 4px 8px 0px rgba(0, 0, 0, 0.5);
--color-shadow-lg: 0px 4px 12px 0px rgba(0, 0, 0, 0.25),
0px 4px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25),
0px 6px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25),
0px 8px 12px 0px rgba(0, 0, 0, 0.6);
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3),
0px 12px 40px 0px rgba(0, 0, 0, 0.65);
}
[data-theme="dark"] {
@@ -219,9 +271,21 @@
--color-border-400: 58, 58, 58; /* strong border- 2 */
/* onboarding colors */
--gradient-onboarding-100: linear-gradient(106deg, #18191b 25.17%, #18191b 99.34%);
--gradient-onboarding-200: linear-gradient(129deg, rgba(47, 49, 53, 0.8) -22.23%, rgba(33, 34, 37, 0.8) 62.98%);
--gradient-onboarding-300: linear-gradient(167deg, rgba(47, 49, 53, 0.45) 19.22%, #212225 98.48%);
--gradient-onboarding-100: linear-gradient(
106deg,
#18191b 25.17%,
#18191b 99.34%
);
--gradient-onboarding-200: linear-gradient(
129deg,
rgba(47, 49, 53, 0.8) -22.23%,
rgba(33, 34, 37, 0.8) 62.98%
);
--gradient-onboarding-300: linear-gradient(
167deg,
rgba(47, 49, 53, 0.45) 19.22%,
#212225 98.48%
);
--color-onboarding-text-100: 237, 238, 240;
--color-onboarding-text-200: 176, 180, 187;
@@ -298,19 +362,37 @@
--color-primary-800: 19, 35, 76;
--color-primary-900: 13, 24, 51;
--color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */
--color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */
--color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */
--color-sidebar-background-100: var(
--color-background-100
); /* primary sidebar bg */
--color-sidebar-background-90: var(
--color-background-90
); /* secondary sidebar bg */
--color-sidebar-background-80: var(
--color-background-80
); /* tertiary sidebar bg */
--color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */
--color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */
--color-sidebar-text-200: var(
--color-text-200
); /* secondary sidebar text */
--color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
--color-sidebar-text-400: var(
--color-text-400
); /* sidebar placeholder text */
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(--color-border-200); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(--color-border-300); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(--color-border-400); /* strong sidebar border- 2 */
--color-sidebar-border-100: var(
--color-border-100
); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(
--color-border-200
); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(
--color-border-300
); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(
--color-border-400
); /* strong sidebar border- 2 */
}
}
@@ -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";
+1
View File
@@ -0,0 +1 @@
export * from "./image-config-form";
+13 -7
View File
@@ -1,15 +1,21 @@
"use client";
import { ReactNode } from "react";
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
// layouts
import { AdminLayout } from "@/layouts";
// lib
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
interface ImageLayoutProps {
children: ReactNode;
}
export const metadata: Metadata = {
title: "Images Settings - God Mode",
};
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
const ImageLayout = ({ children }: ImageLayoutProps) => (
<InstanceWrapper>
<AuthWrapper>
<AdminLayout>{children}</AdminLayout>
</AuthWrapper>
</InstanceWrapper>
);
export default ImageLayout;
+2 -3
View File
@@ -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
+36 -38
View File
@@ -1,48 +1,46 @@
"use client";
import { ReactNode } from "react";
import { ThemeProvider, useTheme } from "next-themes";
import { SWRConfig } from "swr";
// ui
import { Toast } from "@plane/ui";
// constants
import { SWR_CONFIG } from "@/constants/swr-config";
// helpers
import { ASSET_PREFIX, resolveGeneralTheme } from "@/helpers/common.helper";
import { ThemeProvider } from "next-themes";
// lib
import { InstanceProvider } from "@/lib/instance-provider";
import { StoreProvider } from "@/lib/store-provider";
import { UserProvider } from "@/lib/user-provider";
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";
// styles
import "./globals.css";
function RootLayout({ children }: { children: ReactNode }) {
// themes
const { resolvedTheme } = useTheme();
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`}>
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<Toast theme={resolveGeneralTheme(resolvedTheme)} />
<SWRConfig value={SWR_CONFIG}>
<StoreProvider>
<InstanceProvider>
<UserProvider>{children}</UserProvider>
</InstanceProvider>
</StoreProvider>
</SWRConfig>
</ThemeProvider>
</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;
+23 -27
View File
@@ -1,30 +1,26 @@
import { Metadata } from "next";
// components
import { InstanceSignInForm } from "@/components/login";
"use client";
// layouts
import { DefaultLayout } from "@/layouts/default-layout";
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";
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",
},
};
const LoginPage = () => (
<>
<PageHeader title="Login - God Mode" />
<InstanceWrapper pageType={EInstancePageType.POST_SETUP}>
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>
<DefaultLayout>
<InstanceSignInForm />
</DefaultLayout>
</AuthWrapper>
</InstanceWrapper>
</>
);
export default async function LoginPage() {
return (
<DefaultLayout>
<InstanceSignInForm />
</DefaultLayout>
);
}
export default LoginPage;
+1
View File
@@ -0,0 +1 @@
export * from "./sign-up-form";
@@ -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();
@@ -64,18 +64,11 @@ export const InstanceSetupForm: FC = (props) => {
const errorCode = searchParams.get("error_code") || undefined;
const errorMessage = searchParams.get("error_message") || undefined;
// state
const [showPassword, setShowPassword] = useState({
password: false,
retypePassword: false,
});
const [showPassword, setShowPassword] = useState(false);
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
const [formData, setFormData] = useState<TFormData>(defaultFromData);
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false);
const handleShowPassword = (key: keyof typeof showPassword) =>
setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
setFormData((prev) => ({ ...prev, [key]: value }));
@@ -128,12 +121,8 @@ export const InstanceSetupForm: FC = (props) => {
[formData.confirm_password, formData.email, formData.first_name, formData.password, isSubmitting]
);
const password = formData?.password ?? "";
const confirmPassword = formData?.confirm_password ?? "";
const renderPasswordMatchError = !isRetryPasswordInputFocused || confirmPassword.length >= password.length;
return (
<div className="max-w-lg lg:max-w-md w-full">
<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">
@@ -159,13 +148,13 @@ export const InstanceSetupForm: FC = (props) => {
>
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<div className="flex flex-col sm:flex-row items-center gap-4">
<div className="flex items-center gap-4">
<div className="w-full space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="first_name">
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"
@@ -178,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"
@@ -198,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"
@@ -215,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"
@@ -235,10 +224,10 @@ 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.password ? "text" : "password"}
type={showPassword ? "text" : "password"}
inputSize="md"
placeholder="New password..."
value={formData.password}
@@ -247,21 +236,19 @@ export const InstanceSetupForm: FC = (props) => {
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
/>
{showPassword.password ? (
{showPassword ? (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => handleShowPassword("password")}
onClick={() => setShowPassword(false)}
>
<EyeOff className="h-4 w-4" />
</button>
) : (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => handleShowPassword("password")}
onClick={() => setShowPassword(true)}
>
<Eye className="h-4 w-4" />
</button>
@@ -275,11 +262,11 @@ 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
type={showPassword.retypePassword ? "text" : "password"}
type={showPassword ? "text" : "password"}
id="confirm_password"
name="confirm_password"
inputSize="md"
@@ -287,32 +274,28 @@ 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.retypePassword ? (
{showPassword ? (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => handleShowPassword("retypePassword")}
onClick={() => setShowPassword(false)}
>
<EyeOff className="h-4 w-4" />
</button>
) : (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => handleShowPassword("retypePassword")}
onClick={() => setShowPassword(true)}
>
<Eye className="h-4 w-4" />
</button>
)}
</div>
{!!formData.confirm_password &&
formData.password !== formData.confirm_password &&
renderPasswordMatchError && <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">
@@ -331,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>
+19
View File
@@ -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;
+16
View File
@@ -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
+1 -1
View File
@@ -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 = [
{
+2 -2
View File
@@ -1,16 +1,16 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react-lite";
import { usePathname } from "next/navigation";
// mobx
import { observer } from "mobx-react-lite";
// ui
import { Settings } from "lucide-react";
// icons
import { Breadcrumbs } from "@plane/ui";
// components
import { 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 -7
View File
@@ -2,10 +2,10 @@
import React, { useState } from "react";
import { Controller, Control } from "react-hook-form";
// icons
import { Eye, EyeOff } from "lucide-react";
// ui
import { Input } from "@plane/ui";
// icons
import { Eye, EyeOff } from "lucide-react";
// helpers
import { cn } from "@/helpers/common.helper";
@@ -37,9 +37,7 @@ export const ControllerInput: React.FC<Props> = (props) => {
return (
<div className="flex flex-col gap-1">
<h4 className="text-sm text-custom-text-300">
{label} {!required && "(optional)"}
</h4>
<h4 className="text-sm text-custom-text-300">{label}</h4>
<div className="relative">
<Controller
control={control}
@@ -64,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)}
>
@@ -72,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 -2
View File
@@ -2,9 +2,9 @@
import React from "react";
// ui
import { Copy } from "lucide-react";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Copy } from "lucide-react";
type Props = {
label: string;
@@ -40,7 +40,7 @@ export const CopyField: React.FC<Props> = (props) => {
<p className="text-sm font-medium">{url}</p>
<Copy size={18} color="#B9B9B9" />
</Button>
<div className="text-xs text-custom-text-400">{description}</div>
<p className="text-xs text-custom-text-400">{description}</p>
</div>
);
};
-2
View File
@@ -5,5 +5,3 @@ export * from "./copy-field";
export * from "./password-strength-meter";
export * from "./banner";
export * from "./empty-state";
export * from "./logo-spinner";
export * from "./toast";
-17
View File
@@ -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" priority={false} />
</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;
@@ -43,7 +43,7 @@ export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
];
return (
<div className="w-full">
<div className="w-full p-1">
<div className="flex w-full gap-1.5">
{bars.map((color, index) => (
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
-11
View File
@@ -1,11 +0,0 @@
import { useTheme } from "next-themes";
// ui
import { Toast as ToastComponent } from "@plane/ui";
// helpers
import { resolveGeneralTheme } from "@/helpers/common.helper";
export const Toast = () => {
const { theme } = useTheme();
return <ToastComponent theme={resolveGeneralTheme(theme)} />;
};
-2
View File
@@ -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">
<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";
+9 -11
View File
@@ -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));
@@ -94,7 +92,7 @@ export const InstanceSignInForm: FC = (props) => {
);
return (
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<div className="relative w-full h-full overflow-hidden container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 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">
@@ -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"}
+2 -2
View File
@@ -17,11 +17,11 @@ import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
export const NewUserPopup: React.FC = observer(() => {
// hooks
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
const { config } = useInstance();
const { instance } = useInstance();
// theme
const { resolvedTheme } = nextUseTheme();
const redirectionLink = `${config?.app_base_url ? `${config?.app_base_url}/create-workspace` : `/god-mode/`}`;
const redirectionLink = `${instance?.config?.app_base_url ? `${instance?.config?.app_base_url}/create-workspace` : `/god-mode/`}`;
if (!isNewUserPopup) return <></>;
return (
+52 -62
View File
@@ -1,14 +1,10 @@
import { ReactNode } from "react";
import Link from "next/link";
// helpers
import { SUPPORT_EMAIL } from "./common.helper";
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 {
@@ -22,27 +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",
ADMIN_USER_DEACTIVATED = "5190",
}
export type TAuthErrorInfo = {
type: EErrorAlertType;
code: EAuthenticationErrorCodes;
@@ -53,58 +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.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
Sign In
</Link>
&nbsp;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.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
Sign In
</Link>
&nbsp;now.
</div>
),
},
[EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED]: {
title: `User account deactivated`,
message: () => `User account deactivated. Please contact ${!!SUPPORT_EMAIL ? SUPPORT_EMAIL : "administrator"}.`,
title: "User does not exist",
message: () => "User does not exist. Please sign up.",
},
};
@@ -112,18 +92,28 @@ export const authErrorHandler = (
errorCode: EAuthenticationErrorCodes,
email?: string | undefined
): TAuthErrorInfo | undefined => {
const bannerAlertErrorCodes = [
const toastAlertErrorCodes = [
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
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,
EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED,
];
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,
-2
View File
@@ -10,8 +10,6 @@ export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "";
export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || "";
export const SUPPORT_EMAIL = process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "";
export const ASSET_PREFIX = ADMIN_BASE_PATH;
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-provider";
import { StoreContext } from "@/lib/store-context";
import { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-provider";
import { StoreContext } from "@/lib/store-context";
import { IThemeStore } from "@/store/theme.store";
export const useTheme = (): IThemeStore => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-provider";
import { StoreContext } from "@/lib/store-context";
import { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {
+3 -26
View File
@@ -1,38 +1,15 @@
"use client";
import { FC, ReactNode, useEffect } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/navigation";
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 { 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();
const { isUserLoggedIn } = useUser();
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">
@@ -44,4 +21,4 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
<NewUserPopup />
</div>
);
});
};
+9 -11
View File
@@ -2,13 +2,11 @@
import { FC, ReactNode } from "react";
import Image from "next/image";
import Link from "next/link";
import { useTheme } from "next-themes";
// logo/ images
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png";
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png";
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
type TDefaultLayout = {
children: ReactNode;
@@ -19,26 +17,26 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
const { children, withoutBackground = false } = props;
// hooks
const { resolvedTheme } = useTheme();
const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern;
const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo;
return (
<div className="relative">
<div className="h-screen w-full overflow-hidden overflow-y-auto flex flex-col">
<div className="container h-[110px] flex-shrink-0 mx-auto px-5 lg:px-0 flex items-center justify-between gap-5 z-50">
<div className="flex items-center gap-x-2 py-10">
<Link href={`/`} className="h-[30px] w-[133px]">
<Image src={logo} alt="Plane logo" />
</Link>
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
</div>
</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 flex-grow">{children}</div>
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
</div>
</div>
);
+2
View File
@@ -0,0 +1,2 @@
export * from "./default-layout";
export * from "./admin-layout";
-55
View File
@@ -1,55 +0,0 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// components
import { LogoSpinner } from "@/components/common";
import { InstanceSetupForm, InstanceFailureView } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks/store";
// layout
import { DefaultLayout } from "@/layouts/default-layout";
type InstanceProviderProps = {
children: ReactNode;
};
export const InstanceProvider: FC<InstanceProviderProps> = observer((props) => {
const { children } = props;
// store hooks
const { instance, error, fetchInstanceInfo } = useInstance();
// fetching instance details
useSWR("INSTANCE_DETAILS", () => fetchInstanceInfo(), {
revalidateOnFocus: false,
revalidateIfStale: false,
errorRetryCount: 0,
});
if (!instance && !error)
return (
<div className="flex h-screen min-h-[500px] w-full justify-center items-center">
<LogoSpinner />
</div>
);
if (error) {
return (
<DefaultLayout>
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
<InstanceFailureView />
</div>
</DefaultLayout>
);
}
if (!instance?.is_setup_done) {
return (
<DefaultLayout>
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
<InstanceSetupForm />
</div>
</DefaultLayout>
);
}
return <>{children}</>;
});
+21
View File
@@ -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>;
};
-34
View File
@@ -1,34 +0,0 @@
"use client";
import { ReactNode, createContext } from "react";
// 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) {
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 StoreProviderProps = {
children: ReactNode;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialState?: any;
};
export const StoreProvider = ({ children, initialState = {} }: StoreProviderProps) => {
const store = initializeStore(initialState);
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};
-31
View File
@@ -1,31 +0,0 @@
"use client";
import { FC, ReactNode, useEffect } from "react";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// hooks
import { useInstance, useTheme, useUser } from "@/hooks/store";
interface IUserProvider {
children: ReactNode;
}
export const UserProvider: FC<IUserProvider> = observer(({ children }) => {
// hooks
const { isSidebarCollapsed, toggleSidebar } = useTheme();
const { currentUser, fetchCurrentUser } = useUser();
const { fetchInstanceAdmins } = useInstance();
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
shouldRetryOnError: false,
});
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
useEffect(() => {
const localValue = localStorage && localStorage.getItem("god_mode_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
if (isSidebarCollapsed === undefined && localBoolValue != isSidebarCollapsed) toggleSidebar(localBoolValue);
}, [isSidebarCollapsed, currentUser, toggleSidebar]);
return <>{children}</>;
});
+36
View File
@@ -0,0 +1,36 @@
"use client";
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";
interface IAppWrapper {
children: ReactNode;
}
export const AppWrapper: FC<IAppWrapper> = observer(({ children }) => {
// hooks
const { theme, isSidebarCollapsed, toggleSidebar } = useTheme();
const { currentUser } = useUser();
useEffect(() => {
const localValue = localStorage && localStorage.getItem("god_mode_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
if (isSidebarCollapsed === undefined && localBoolValue != isSidebarCollapsed) toggleSidebar(localBoolValue);
}, [isSidebarCollapsed, currentUser, toggleSidebar]);
return (
<Suspense>
<Toast theme={resolveGeneralTheme(theme)} />
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
</Suspense>
);
});
+59
View File
@@ -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}</>;
});
+3
View File
@@ -0,0 +1,3 @@
export * from "./app-wrapper";
export * from "./instance-wrapper";
export * from "./auth-wrapper";
+65
View File
@@ -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}</>;
});
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "admin",
"version": "0.20.0",
"version": "0.17.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
@@ -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: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

+9 -9
View File
@@ -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>> {
+2 -2
View File
@@ -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;
+3
View File
@@ -0,0 +1,3 @@
export * from "./auth.service";
export * from "./instance.service";
export * from "./user.service";
+6 -12
View File
@@ -1,11 +1,5 @@
// types
import type {
IFormattedInstanceConfiguration,
IInstance,
IInstanceAdmin,
IInstanceConfiguration,
IInstanceInfo,
} from "@plane/types";
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
import { APIService } from "@/services/api.service";
@@ -15,11 +9,11 @@ export class InstanceService extends APIService {
super(API_BASE_URL);
}
async getInstanceInfo(): Promise<IInstanceInfo> {
return this.get<IInstanceInfo>("/api/instances/")
async getInstanceInfo(): Promise<IInstance> {
return this.get<IInstance>("/api/instances/")
.then((response) => response.data)
.catch((error) => {
throw error?.response?.data;
throw error;
});
}
@@ -31,8 +25,8 @@ export class InstanceService extends APIService {
});
}
async updateInstanceInfo(data: Partial<IInstance>): Promise<IInstance> {
return this.patch<Partial<IInstance>, IInstance>("/api/instances/", data)
async updateInstanceInfo(data: Partial<IInstance["instance"]>): Promise<IInstance["instance"]> {
return this.patch<Partial<IInstance["instance"]>, IInstance["instance"]>("/api/instances/", data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
+2 -12
View File
@@ -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)
+17 -45
View File
@@ -1,46 +1,34 @@
import set from "lodash/set";
import { observable, action, computed, makeObservable, runInAction } from "mobx";
import {
IInstance,
IInstanceAdmin,
IInstanceConfiguration,
IFormattedInstanceConfiguration,
IInstanceInfo,
IInstanceConfig,
} from "@plane/types";
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
isLoading: boolean;
error: any;
instanceStatus: TInstanceStatus | undefined;
instance: IInstance | undefined;
config: IInstanceConfig | undefined;
instanceAdmins: IInstanceAdmin[] | undefined;
instanceConfigurations: IInstanceConfiguration[] | undefined;
// computed
formattedConfig: IFormattedInstanceConfiguration | undefined;
// action
hydrate: (data: IInstanceInfo) => void;
fetchInstanceInfo: () => Promise<IInstanceInfo | undefined>;
updateInstanceInfo: (data: Partial<IInstance>) => Promise<IInstance | undefined>;
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 {
isLoading: boolean = true;
error: any = undefined;
instanceStatus: TInstanceStatus | undefined = undefined;
instance: IInstance | undefined = undefined;
config: IInstanceConfig | undefined = undefined;
instanceAdmins: IInstanceAdmin[] | undefined = undefined;
instanceConfigurations: IInstanceConfiguration[] | undefined = undefined;
// service
@@ -50,7 +38,6 @@ export class InstanceStore implements IInstanceStore {
makeObservable(this, {
// observable
isLoading: observable.ref,
error: observable.ref,
instanceStatus: observable,
instance: observable,
instanceAdmins: observable,
@@ -58,7 +45,6 @@ export class InstanceStore implements IInstanceStore {
// computed
formattedConfig: computed,
// actions
hydrate: action,
fetchInstanceInfo: action,
fetchInstanceAdmins: action,
updateInstanceInfo: action,
@@ -69,13 +55,6 @@ export class InstanceStore implements IInstanceStore {
this.instanceService = new InstanceService();
}
hydrate = (data: IInstanceInfo) => {
if (data) {
this.instance = data.instance;
this.config = data.config;
}
};
/**
* computed value for instance configurations data for forms.
* @returns configurations in the form of {key, value} pair.
@@ -95,22 +74,17 @@ export class InstanceStore implements IInstanceStore {
fetchInstanceInfo = async () => {
try {
if (this.instance === undefined) this.isLoading = true;
this.error = undefined;
const instanceInfo = await this.instanceService.getInstanceInfo();
const instance = await this.instanceService.getInstanceInfo();
// handling the new user popup toggle
if (this.instance === undefined && !instanceInfo?.instance?.workspaces_exist)
this.store.theme.toggleNewUserPopup();
if (this.instance === undefined && !instance?.instance?.workspaces_exist) this.store.theme.toggleNewUserPopup();
runInAction(() => {
console.log("instanceInfo: ", instanceInfo);
this.isLoading = false;
this.instance = instanceInfo.instance;
this.config = instanceInfo.config;
this.instance = instance;
});
return instanceInfo;
return instance;
} catch (error) {
console.error("Error fetching the instance info");
this.isLoading = false;
this.error = { message: "Failed to fetch the instance info" };
this.instanceStatus = {
status: EInstanceStatus.ERROR,
};
@@ -120,10 +94,10 @@ export class InstanceStore implements IInstanceStore {
/**
* @description updating instance information
* @param {Partial<IInstance>} data
* @param {Partial<IInstance["instance"]>} data
* @returns void
*/
updateInstanceInfo = async (data: Partial<IInstance>) => {
updateInstanceInfo = async (data: Partial<IInstance["instance"]>) => {
try {
const instanceResponse = await this.instanceService.updateInstanceInfo(data);
if (instanceResponse) {
@@ -174,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
View File
@@ -1,6 +1,6 @@
import { action, observable, makeObservable } from "mobx";
// root store
import { RootStore } from "@/store/root.store";
import { RootStore } from "@/store/root-store";
type TTheme = "dark" | "light";
export interface IThemeStore {
@@ -9,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
*/
+2 -7
View File
@@ -3,10 +3,10 @@ import { IUser } from "@plane/types";
// helpers
import { EUserStatus, TUserStatus } from "@/helpers";
// services
import { AuthService } from "@/services/auth.service";
import { UserService } from "@/services/user.service";
// root store
import { RootStore } from "@/store/root.store";
import { 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>
+1 -2
View File
@@ -12,8 +12,7 @@ POSTGRES_USER="plane"
POSTGRES_PASSWORD="plane"
POSTGRES_HOST="plane-db"
POSTGRES_DB="plane"
POSTGRES_PORT=5432
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}
# Redis Settings

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