diff --git a/apiserver/plane/api/views/config.py b/apiserver/plane/api/views/config.py
index 687cb211c..a06a7f7fc 100644
--- a/apiserver/plane/api/views/config.py
+++ b/apiserver/plane/api/views/config.py
@@ -21,8 +21,8 @@ class ConfigurationEndpoint(BaseAPIView):
def get(self, request):
data = {}
- data["google"] = os.environ.get("GOOGLE_CLIENT_ID", None)
- data["github"] = os.environ.get("GITHUB_CLIENT_ID", None)
+ data["google_client_id"] = os.environ.get("GOOGLE_CLIENT_ID", None)
+ data["github_client_id"] = os.environ.get("GITHUB_CLIENT_ID", None)
data["github_app_name"] = os.environ.get("GITHUB_APP_NAME", None)
data["magic_login"] = (
bool(settings.EMAIL_HOST_USER) and bool(settings.EMAIL_HOST_PASSWORD)
@@ -30,5 +30,5 @@ class ConfigurationEndpoint(BaseAPIView):
data["email_password_login"] = (
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1"
)
- data["slack"] = os.environ.get("SLACK_CLIENT_ID", None)
+ data["slack_client_id"] = os.environ.get("SLACK_CLIENT_ID", None)
return Response(data, status=status.HTTP_200_OK)
diff --git a/space/components/accounts/sign-in.tsx b/space/components/accounts/sign-in.tsx
index 0c6810198..b55824e6c 100644
--- a/space/components/accounts/sign-in.tsx
+++ b/space/components/accounts/sign-in.tsx
@@ -116,7 +116,9 @@ export const SignInView = observer(() => {
)}
- {data?.google && }
+ {data?.google_client_id && (
+
+ )}
diff --git a/space/services/app-config.service.ts b/space/services/app-config.service.ts
index 713cda3da..09a6989ef 100644
--- a/space/services/app-config.service.ts
+++ b/space/services/app-config.service.ts
@@ -3,12 +3,13 @@ import APIService from "services/api.service";
// helper
import { API_BASE_URL } from "helpers/common.helper";
-export interface IEnvConfig {
- github: string;
- google: string;
- github_app_name: string | null;
+export interface IAppConfig {
email_password_login: boolean;
+ google_client_id: string | null;
+ github_app_name: string | null;
+ github_client_id: string | null;
magic_login: boolean;
+ slack_client_id: string | null;
}
export class AppConfigService extends APIService {
@@ -16,7 +17,7 @@ export class AppConfigService extends APIService {
super(API_BASE_URL);
}
- async envConfig(): Promise {
+ async envConfig(): Promise {
return this.get("/api/configs/", {
headers: {
"Content-Type": "application/json",
diff --git a/turbo.json b/turbo.json
index 7c3ccb81a..ac462d08b 100644
--- a/turbo.json
+++ b/turbo.json
@@ -5,7 +5,6 @@
"NEXT_PUBLIC_DEPLOY_URL",
"NEXT_PUBLIC_SENTRY_DSN",
"NEXT_PUBLIC_SENTRY_ENVIRONMENT",
- "NEXT_PUBLIC_GITHUB_APP_NAME",
"NEXT_PUBLIC_ENABLE_SENTRY",
"NEXT_PUBLIC_ENABLE_OAUTH",
"NEXT_PUBLIC_TRACK_EVENTS",
@@ -22,8 +21,7 @@
"SLACK_CLIENT_SECRET",
"JITSU_TRACKER_ACCESS_KEY",
"JITSU_TRACKER_HOST",
- "UNSPLASH_ACCESS_KEY",
- "NEXT_PUBLIC_SLACK_CLIENT_ID"
+ "UNSPLASH_ACCESS_KEY"
],
"pipeline": {
"build": {
diff --git a/web/components/integration/github/auth.tsx b/web/components/integration/github/auth.tsx
index c94bfacd5..9d5816f3b 100644
--- a/web/components/integration/github/auth.tsx
+++ b/web/components/integration/github/auth.tsx
@@ -4,14 +4,24 @@ import useIntegrationPopup from "hooks/use-integration-popup";
import { Button } from "@plane/ui";
// types
import { IWorkspaceIntegration } from "types";
+import { observer } from "mobx-react-lite";
+import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
workspaceIntegration: false | IWorkspaceIntegration | undefined;
provider: string | undefined;
};
-export const GithubAuth: React.FC = ({ workspaceIntegration, provider }) => {
- const { startAuth, isConnecting } = useIntegrationPopup(provider);
+export const GithubAuth: React.FC = observer(({ workspaceIntegration, provider }) => {
+ const {
+ appConfig: { envConfig },
+ } = useMobxStore();
+ // hooks
+ const { startAuth, isConnecting } = useIntegrationPopup({
+ provider,
+ github_app_name: envConfig?.github_app_name || "",
+ slack_client_id: envConfig?.slack_client_id || "",
+ });
return (
@@ -26,4 +36,4 @@ export const GithubAuth: React.FC
= ({ workspaceIntegration, provider })
)}
);
-};
+});
diff --git a/web/components/integration/single-integration-card.tsx b/web/components/integration/single-integration-card.tsx
index 28fca6fcd..999a12bb5 100644
--- a/web/components/integration/single-integration-card.tsx
+++ b/web/components/integration/single-integration-card.tsx
@@ -20,6 +20,8 @@ import { CheckCircle } from "lucide-react";
import { IAppIntegration, IWorkspaceIntegration } from "types";
// fetch-keys
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
+import { observer } from "mobx-react-lite";
+import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
integration: IAppIntegration;
@@ -41,7 +43,11 @@ const integrationDetails: { [key: string]: any } = {
// services
const integrationService = new IntegrationService();
-export const SingleIntegrationCard: React.FC = ({ integration }) => {
+export const SingleIntegrationCard: React.FC = observer(({ integration }) => {
+ const {
+ appConfig: { envConfig },
+ } = useMobxStore();
+
const [deletingIntegration, setDeletingIntegration] = useState(false);
const router = useRouter();
@@ -49,7 +55,11 @@ export const SingleIntegrationCard: React.FC = ({ integration }) => {
const { setToastAlert } = useToast();
- const { startAuth, isConnecting: isInstalling } = useIntegrationPopup(integration.provider);
+ const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({
+ provider: integration.provider,
+ github_app_name: envConfig?.github_app_name || "",
+ slack_client_id: envConfig?.slack_client_id || "",
+ });
const { data: workspaceIntegrations } = useSWR(
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
@@ -132,4 +142,4 @@ export const SingleIntegrationCard: React.FC = ({ integration }) => {
)}
);
-};
+});
diff --git a/web/components/integration/slack/select-channel.tsx b/web/components/integration/slack/select-channel.tsx
index fb5393f3a..eaaa3daee 100644
--- a/web/components/integration/slack/select-channel.tsx
+++ b/web/components/integration/slack/select-channel.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
+import { observer } from "mobx-react-lite";
// services
import { AppInstallationService } from "services/app_installation.service";
// ui
@@ -11,6 +12,8 @@ import useIntegrationPopup from "hooks/use-integration-popup";
import { IWorkspaceIntegration, ISlackIntegration } from "types";
// fetch-keys
import { SLACK_CHANNEL_INFO } from "constants/fetch-keys";
+// lib
+import { useMobxStore } from "lib/mobx/store-provider";
type Props = {
integration: IWorkspaceIntegration;
@@ -18,14 +21,24 @@ type Props = {
const appInstallationService = new AppInstallationService();
-export const SelectChannel: React.FC = ({ integration }) => {
+export const SelectChannel: React.FC = observer(({ integration }) => {
+ // store
+ const {
+ appConfig: { envConfig },
+ } = useMobxStore();
+ // states
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState(false);
const [slackChannel, setSlackChannel] = useState(null);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
- const { startAuth } = useIntegrationPopup("slackChannel", integration.id);
+ const { startAuth } = useIntegrationPopup({
+ provider: "slackChannel",
+ stateParams: integration.id,
+ github_app_name: envConfig?.github_client_id || "",
+ slack_client_id: envConfig?.slack_client_id || "",
+ });
const { data: projectIntegration } = useSWR(
workspaceSlug && projectId && integration.id
@@ -97,4 +110,4 @@ export const SelectChannel: React.FC = ({ integration }) => {
)}
>
);
-};
+});
diff --git a/web/components/page-views/signin.tsx b/web/components/page-views/signin.tsx
index ccaa7d2c3..547170632 100644
--- a/web/components/page-views/signin.tsx
+++ b/web/components/page-views/signin.tsx
@@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from "react";
-import useSWR from "swr";
import { observer } from "mobx-react-lite";
import Image from "next/image";
import { useRouter } from "next/router";
@@ -8,7 +7,6 @@ import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider";
// services
import { AuthService } from "services/auth.service";
-import { AppConfigService } from "services/app_config.service";
// components
import {
GoogleLoginButton,
@@ -24,12 +22,12 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
// types
import { IUser, IUserSettings } from "types";
-const appConfigService = new AppConfigService();
const authService = new AuthService();
export const SignInView = observer(() => {
const {
user: { fetchCurrentUser, fetchCurrentUserSettings },
+ appConfig: { envConfig },
} = useMobxStore();
// router
const router = useRouter();
@@ -38,12 +36,16 @@ export const SignInView = observer(() => {
const [isLoading, setLoading] = useState(false);
// toast
const { setToastAlert } = useToast();
- // fetch app config
- const { data, error: appConfigError } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
// computed
const enableEmailPassword =
- data &&
- (data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github));
+ envConfig &&
+ (envConfig?.email_password_login ||
+ !(
+ envConfig?.email_password_login ||
+ envConfig?.magic_login ||
+ envConfig?.google_client_id ||
+ envConfig?.github_client_id
+ ));
const handleLoginRedirection = useCallback(
(user: IUser) => {
@@ -114,11 +116,11 @@ export const SignInView = observer(() => {
const handleGitHubSignIn = async (credential: string) => {
try {
setLoading(true);
- if (data && data.github && credential) {
+ if (envConfig && envConfig.github_client_id && credential) {
const socialAuthPayload = {
medium: "github",
credential,
- clientId: data.github,
+ clientId: envConfig.github_client_id,
};
const response = await authService.socialAuth(socialAuthPayload);
if (response) {
@@ -195,7 +197,7 @@ export const SignInView = observer(() => {
Sign in to Plane
- {!data && !appConfigError ? (
+ {!envConfig ? (
@@ -211,7 +213,7 @@ export const SignInView = observer(() => {
<>
<>
{enableEmailPassword && }
- {data?.magic_login && (
+ {envConfig?.magic_login && (
@@ -219,8 +221,12 @@ export const SignInView = observer(() => {
)}
- {data?.google && }
- {data?.github && }
+ {envConfig?.google_client_id && (
+
+ )}
+ {envConfig?.github_client_id && (
+
+ )}
>
diff --git a/web/hooks/use-integration-popup.tsx b/web/hooks/use-integration-popup.tsx
index 58cfbc009..fb9aab223 100644
--- a/web/hooks/use-integration-popup.tsx
+++ b/web/hooks/use-integration-popup.tsx
@@ -1,23 +1,26 @@
import { useRef, useState } from "react";
-
import { useRouter } from "next/router";
-const useIntegrationPopup = (provider: string | undefined, stateParams?: string) => {
+const useIntegrationPopup = ({
+ provider,
+ stateParams,
+ github_app_name,
+ slack_client_id,
+}: {
+ provider: string | undefined;
+ stateParams?: string;
+ github_app_name?: string;
+ slack_client_id?: string;
+}) => {
const [authLoader, setAuthLoader] = useState(false);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const providerUrls: { [key: string]: string } = {
- github: `https://github.com/apps/${
- process.env.NEXT_PUBLIC_GITHUB_APP_NAME
- }/installations/new?state=${workspaceSlug?.toString()}`,
- slack: `https://slack.com/oauth/v2/authorize?scope=chat:write,im:history,im:write,links:read,links:write,users:read,users:read.email&user_scope=&&client_id=${
- process.env.NEXT_PUBLIC_SLACK_CLIENT_ID
- }&state=${workspaceSlug?.toString()}`,
- slackChannel: `https://slack.com/oauth/v2/authorize?scope=incoming-webhook&client_id=${
- process.env.NEXT_PUBLIC_SLACK_CLIENT_ID
- }&state=${workspaceSlug?.toString()},${projectId?.toString()}${
+ github: `https://github.com/apps/${github_app_name}/installations/new?state=${workspaceSlug?.toString()}`,
+ slack: `https://slack.com/oauth/v2/authorize?scope=chat:write,im:history,im:write,links:read,links:write,users:read,users:read.email&user_scope=&&client_id=${slack_client_id}&state=${workspaceSlug?.toString()}`,
+ slackChannel: `https://slack.com/oauth/v2/authorize?scope=incoming-webhook&client_id=${slack_client_id}&state=${workspaceSlug?.toString()},${projectId?.toString()}${
stateParams ? "," + stateParams : ""
}`,
};
diff --git a/web/lib/mobx/store-init.tsx b/web/lib/mobx/store-init.tsx
index 780b12d99..66d81b5aa 100644
--- a/web/lib/mobx/store-init.tsx
+++ b/web/lib/mobx/store-init.tsx
@@ -1,11 +1,12 @@
import { useEffect, useState } from "react";
-// next themes
+import { observer } from "mobx-react-lite";
+import useSWR from "swr";
import { useTheme } from "next-themes";
+import { useRouter } from "next/router";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
-import { useRouter } from "next/router";
+// helpers
import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
-import { observer } from "mobx-react-lite";
const MobxStoreInit = observer(() => {
// router
@@ -13,16 +14,19 @@ const MobxStoreInit = observer(() => {
const { workspaceSlug, projectId, cycleId, moduleId, globalViewId, viewId, inboxId } = router.query;
// store
const {
- theme: themeStore,
- user: userStore,
- workspace: workspaceStore,
- project: projectStore,
- cycle: cycleStore,
- module: moduleStore,
- globalViews: globalViewsStore,
- projectViews: projectViewsStore,
- inbox: inboxStore,
+ theme: { sidebarCollapsed, toggleSidebar },
+ user: { currentUser },
+ workspace: { setWorkspaceSlug },
+ project: { setProjectId },
+ cycle: { setCycleId },
+ module: { setModuleId },
+ globalViews: { setGlobalViewId },
+ projectViews: { setViewId },
+ inbox: { setInboxId },
+ appConfig: { fetchAppConfig },
} = useMobxStore();
+ // fetching application Config
+ useSWR("APP_CONFIG", () => fetchAppConfig(), { revalidateIfStale: false, revalidateOnFocus: false });
// state
const [dom, setDom] = useState();
// theme
@@ -34,36 +38,36 @@ const MobxStoreInit = observer(() => {
useEffect(() => {
const localValue = localStorage && localStorage.getItem("app_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
- if (localValue && themeStore?.sidebarCollapsed === undefined) {
- themeStore.toggleSidebar(localBoolValue);
+ if (localValue && sidebarCollapsed === undefined) {
+ toggleSidebar(localBoolValue);
}
- }, [themeStore, userStore, setTheme]);
+ }, [sidebarCollapsed, currentUser, setTheme, toggleSidebar]);
/**
* Setting up the theme of the user by fetching it from local storage
*/
useEffect(() => {
- if (!userStore.currentUser) return;
+ if (!currentUser) return;
if (window) {
setDom(window.document?.querySelector("[data-theme='custom']"));
}
- setTheme(userStore.currentUser?.theme?.theme || "system");
- if (userStore.currentUser?.theme?.theme === "custom" && dom) {
- applyTheme(userStore.currentUser?.theme?.palette, false);
+ setTheme(currentUser?.theme?.theme || "system");
+ if (currentUser?.theme?.theme === "custom" && dom) {
+ applyTheme(currentUser?.theme?.palette, false);
} else unsetCustomCssVariables();
- }, [userStore.currentUser, setTheme, dom]);
+ }, [currentUser, setTheme, dom]);
/**
* Setting router info to the respective stores.
*/
useEffect(() => {
- if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString());
- if (projectId) projectStore.setProjectId(projectId.toString());
- if (cycleId) cycleStore.setCycleId(cycleId.toString());
- if (moduleId) moduleStore.setModuleId(moduleId.toString());
- if (globalViewId) globalViewsStore.setGlobalViewId(globalViewId.toString());
- if (viewId) projectViewsStore.setViewId(viewId.toString());
- if (inboxId) inboxStore.setInboxId(inboxId.toString());
+ if (workspaceSlug) setWorkspaceSlug(workspaceSlug.toString());
+ if (projectId) setProjectId(projectId.toString());
+ if (cycleId) setCycleId(cycleId.toString());
+ if (moduleId) setModuleId(moduleId.toString());
+ if (globalViewId) setGlobalViewId(globalViewId.toString());
+ if (viewId) setViewId(viewId.toString());
+ if (inboxId) setInboxId(inboxId.toString());
}, [
workspaceSlug,
projectId,
@@ -72,13 +76,13 @@ const MobxStoreInit = observer(() => {
globalViewId,
viewId,
inboxId,
- workspaceStore,
- projectStore,
- cycleStore,
- moduleStore,
- globalViewsStore,
- projectViewsStore,
- inboxStore,
+ setWorkspaceSlug,
+ setProjectId,
+ setCycleId,
+ setModuleId,
+ setGlobalViewId,
+ setViewId,
+ setInboxId,
]);
return <>>;
diff --git a/web/services/app_config.service.ts b/web/services/app_config.service.ts
index 5843c01c9..8f8bcd423 100644
--- a/web/services/app_config.service.ts
+++ b/web/services/app_config.service.ts
@@ -2,27 +2,21 @@
import { APIService } from "services/api.service";
// helper
import { API_BASE_URL } from "helpers/common.helper";
-
-export interface IEnvConfig {
- github: string;
- google: string;
- github_app_name: string | null;
- email_password_login: boolean;
- magic_login: boolean;
-}
+// types
+import { IAppConfig } from "types/app";
export class AppConfigService extends APIService {
constructor() {
super(API_BASE_URL);
}
- async envConfig(): Promise {
+ async envConfig(): Promise {
return this.get("/api/configs/", {
headers: {
"Content-Type": "application/json",
},
})
- .then((response) => response?.data)
+ .then((response) => response.data)
.catch((error) => {
throw error?.response?.data;
});
diff --git a/web/store/app-config.store.ts b/web/store/app-config.store.ts
new file mode 100644
index 000000000..3a4d9efc0
--- /dev/null
+++ b/web/store/app-config.store.ts
@@ -0,0 +1,47 @@
+import { observable, action, makeObservable, runInAction } from "mobx";
+// types
+import { RootStore } from "./root";
+import { IAppConfig } from "types/app";
+// services
+import { AppConfigService } from "services/app_config.service";
+
+export interface IAppConfigStore {
+ envConfig: IAppConfig | null;
+ // action
+ fetchAppConfig: () => Promise;
+}
+
+class AppConfigStore implements IAppConfigStore {
+ // observables
+ envConfig: IAppConfig | null = null;
+
+ // root store
+ rootStore;
+ // service
+ appConfigService;
+
+ constructor(_rootStore: RootStore) {
+ makeObservable(this, {
+ // observables
+ envConfig: observable.ref,
+ // actions
+ fetchAppConfig: action,
+ });
+ this.appConfigService = new AppConfigService();
+
+ this.rootStore = _rootStore;
+ }
+ fetchAppConfig = async () => {
+ try {
+ const config = await this.appConfigService.envConfig();
+ runInAction(() => {
+ this.envConfig = config;
+ });
+ return config;
+ } catch (error) {
+ throw error;
+ }
+ };
+}
+
+export default AppConfigStore;
diff --git a/web/store/root.ts b/web/store/root.ts
index 9af4db492..c6d781b28 100644
--- a/web/store/root.ts
+++ b/web/store/root.ts
@@ -1,5 +1,6 @@
import { enableStaticRendering } from "mobx-react-lite";
// store imports
+import AppConfigStore, { IAppConfigStore } from "./app-config.store";
import CommandPaletteStore, { ICommandPaletteStore } from "./command-palette.store";
import UserStore, { IUserStore } from "store/user.store";
import ThemeStore, { IThemeStore } from "store/theme.store";
@@ -107,6 +108,7 @@ enableStaticRendering(typeof window === "undefined");
export class RootStore {
user: IUserStore;
theme: IThemeStore;
+ appConfig: IAppConfigStore;
commandPalette: ICommandPaletteStore;
workspace: IWorkspaceStore;
@@ -167,6 +169,7 @@ export class RootStore {
mentionsStore: IMentionsStore;
constructor() {
+ this.appConfig = new AppConfigStore(this);
this.commandPalette = new CommandPaletteStore(this);
this.user = new UserStore(this);
this.theme = new ThemeStore(this);
diff --git a/web/types/app.d.ts b/web/types/app.d.ts
index 2b03f6975..c762fb76f 100644
--- a/web/types/app.d.ts
+++ b/web/types/app.d.ts
@@ -1,3 +1,12 @@
export type NextPageWithLayout = NextPage
& {
getLayout?: (page: ReactElement) => ReactNode;
};
+
+export interface IAppConfig {
+ email_password_login: boolean;
+ google_client_id: string | null;
+ github_app_name: string | null;
+ github_client_id: string | null;
+ magic_login: boolean;
+ slack_client_id: string | null;
+}