fix: environment config changes in the API are replicated in web and space app (#2699)

* fix: envconfig type changes

* chore: configuration variables  (#2692)

* chore: update avatar group logic (#2672)

* chore: configuration variables

---------

* fix: replacing slack client id with env config

---------

Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
This commit is contained in:
sriram veeraghanta 2023-11-07 17:17:10 +05:30 committed by GitHub
parent 1986c0dfd4
commit 26de35bd8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 189 additions and 89 deletions

View File

@ -21,8 +21,8 @@ class ConfigurationEndpoint(BaseAPIView):
def get(self, request): def get(self, request):
data = {} data = {}
data["google"] = os.environ.get("GOOGLE_CLIENT_ID", None) data["google_client_id"] = os.environ.get("GOOGLE_CLIENT_ID", None)
data["github"] = os.environ.get("GITHUB_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["github_app_name"] = os.environ.get("GITHUB_APP_NAME", None)
data["magic_login"] = ( data["magic_login"] = (
bool(settings.EMAIL_HOST_USER) and bool(settings.EMAIL_HOST_PASSWORD) bool(settings.EMAIL_HOST_USER) and bool(settings.EMAIL_HOST_PASSWORD)
@ -30,5 +30,5 @@ class ConfigurationEndpoint(BaseAPIView):
data["email_password_login"] = ( data["email_password_login"] = (
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1" 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) return Response(data, status=status.HTTP_200_OK)

View File

@ -116,7 +116,9 @@ export const SignInView = observer(() => {
)} )}
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden"> <div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
{data?.google && <GoogleLoginButton clientId={data.google} handleSignIn={handleGoogleSignIn} />} {data?.google_client_id && (
<GoogleLoginButton clientId={data.google_client_id} handleSignIn={handleGoogleSignIn} />
)}
</div> </div>
<p className="pt-16 text-custom-text-200 text-sm text-center"> <p className="pt-16 text-custom-text-200 text-sm text-center">

View File

@ -3,12 +3,13 @@ import APIService from "services/api.service";
// helper // helper
import { API_BASE_URL } from "helpers/common.helper"; import { API_BASE_URL } from "helpers/common.helper";
export interface IEnvConfig { export interface IAppConfig {
github: string;
google: string;
github_app_name: string | null;
email_password_login: boolean; email_password_login: boolean;
google_client_id: string | null;
github_app_name: string | null;
github_client_id: string | null;
magic_login: boolean; magic_login: boolean;
slack_client_id: string | null;
} }
export class AppConfigService extends APIService { export class AppConfigService extends APIService {
@ -16,7 +17,7 @@ export class AppConfigService extends APIService {
super(API_BASE_URL); super(API_BASE_URL);
} }
async envConfig(): Promise<IEnvConfig> { async envConfig(): Promise<IAppConfig> {
return this.get("/api/configs/", { return this.get("/api/configs/", {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -5,7 +5,6 @@
"NEXT_PUBLIC_DEPLOY_URL", "NEXT_PUBLIC_DEPLOY_URL",
"NEXT_PUBLIC_SENTRY_DSN", "NEXT_PUBLIC_SENTRY_DSN",
"NEXT_PUBLIC_SENTRY_ENVIRONMENT", "NEXT_PUBLIC_SENTRY_ENVIRONMENT",
"NEXT_PUBLIC_GITHUB_APP_NAME",
"NEXT_PUBLIC_ENABLE_SENTRY", "NEXT_PUBLIC_ENABLE_SENTRY",
"NEXT_PUBLIC_ENABLE_OAUTH", "NEXT_PUBLIC_ENABLE_OAUTH",
"NEXT_PUBLIC_TRACK_EVENTS", "NEXT_PUBLIC_TRACK_EVENTS",
@ -22,8 +21,7 @@
"SLACK_CLIENT_SECRET", "SLACK_CLIENT_SECRET",
"JITSU_TRACKER_ACCESS_KEY", "JITSU_TRACKER_ACCESS_KEY",
"JITSU_TRACKER_HOST", "JITSU_TRACKER_HOST",
"UNSPLASH_ACCESS_KEY", "UNSPLASH_ACCESS_KEY"
"NEXT_PUBLIC_SLACK_CLIENT_ID"
], ],
"pipeline": { "pipeline": {
"build": { "build": {

View File

@ -4,14 +4,24 @@ import useIntegrationPopup from "hooks/use-integration-popup";
import { Button } from "@plane/ui"; import { Button } from "@plane/ui";
// types // types
import { IWorkspaceIntegration } from "types"; import { IWorkspaceIntegration } from "types";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {
workspaceIntegration: false | IWorkspaceIntegration | undefined; workspaceIntegration: false | IWorkspaceIntegration | undefined;
provider: string | undefined; provider: string | undefined;
}; };
export const GithubAuth: React.FC<Props> = ({ workspaceIntegration, provider }) => { export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => {
const { startAuth, isConnecting } = useIntegrationPopup(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 ( return (
<div> <div>
@ -26,4 +36,4 @@ export const GithubAuth: React.FC<Props> = ({ workspaceIntegration, provider })
)} )}
</div> </div>
); );
}; });

View File

@ -20,6 +20,8 @@ import { CheckCircle } from "lucide-react";
import { IAppIntegration, IWorkspaceIntegration } from "types"; import { IAppIntegration, IWorkspaceIntegration } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {
integration: IAppIntegration; integration: IAppIntegration;
@ -41,7 +43,11 @@ const integrationDetails: { [key: string]: any } = {
// services // services
const integrationService = new IntegrationService(); const integrationService = new IntegrationService();
export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => { export const SingleIntegrationCard: React.FC<Props> = observer(({ integration }) => {
const {
appConfig: { envConfig },
} = useMobxStore();
const [deletingIntegration, setDeletingIntegration] = useState(false); const [deletingIntegration, setDeletingIntegration] = useState(false);
const router = useRouter(); const router = useRouter();
@ -49,7 +55,11 @@ export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
const { setToastAlert } = useToast(); 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( const { data: workspaceIntegrations } = useSWR(
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null, workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
@ -132,4 +142,4 @@ export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
)} )}
</div> </div>
); );
}; });

View File

@ -1,6 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { observer } from "mobx-react-lite";
// services // services
import { AppInstallationService } from "services/app_installation.service"; import { AppInstallationService } from "services/app_installation.service";
// ui // ui
@ -11,6 +12,8 @@ import useIntegrationPopup from "hooks/use-integration-popup";
import { IWorkspaceIntegration, ISlackIntegration } from "types"; import { IWorkspaceIntegration, ISlackIntegration } from "types";
// fetch-keys // fetch-keys
import { SLACK_CHANNEL_INFO } from "constants/fetch-keys"; import { SLACK_CHANNEL_INFO } from "constants/fetch-keys";
// lib
import { useMobxStore } from "lib/mobx/store-provider";
type Props = { type Props = {
integration: IWorkspaceIntegration; integration: IWorkspaceIntegration;
@ -18,14 +21,24 @@ type Props = {
const appInstallationService = new AppInstallationService(); const appInstallationService = new AppInstallationService();
export const SelectChannel: React.FC<Props> = ({ integration }) => { export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
// store
const {
appConfig: { envConfig },
} = useMobxStore();
// states
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false); const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null); const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; 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( const { data: projectIntegration } = useSWR(
workspaceSlug && projectId && integration.id workspaceSlug && projectId && integration.id
@ -97,4 +110,4 @@ export const SelectChannel: React.FC<Props> = ({ integration }) => {
)} )}
</> </>
); );
}; });

View File

@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import useSWR from "swr";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -8,7 +7,6 @@ import useToast from "hooks/use-toast";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// services // services
import { AuthService } from "services/auth.service"; import { AuthService } from "services/auth.service";
import { AppConfigService } from "services/app_config.service";
// components // components
import { import {
GoogleLoginButton, GoogleLoginButton,
@ -24,12 +22,12 @@ import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
// types // types
import { IUser, IUserSettings } from "types"; import { IUser, IUserSettings } from "types";
const appConfigService = new AppConfigService();
const authService = new AuthService(); const authService = new AuthService();
export const SignInView = observer(() => { export const SignInView = observer(() => {
const { const {
user: { fetchCurrentUser, fetchCurrentUserSettings }, user: { fetchCurrentUser, fetchCurrentUserSettings },
appConfig: { envConfig },
} = useMobxStore(); } = useMobxStore();
// router // router
const router = useRouter(); const router = useRouter();
@ -38,12 +36,16 @@ export const SignInView = observer(() => {
const [isLoading, setLoading] = useState(false); const [isLoading, setLoading] = useState(false);
// toast // toast
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// fetch app config
const { data, error: appConfigError } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
// computed // computed
const enableEmailPassword = const enableEmailPassword =
data && envConfig &&
(data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github)); (envConfig?.email_password_login ||
!(
envConfig?.email_password_login ||
envConfig?.magic_login ||
envConfig?.google_client_id ||
envConfig?.github_client_id
));
const handleLoginRedirection = useCallback( const handleLoginRedirection = useCallback(
(user: IUser) => { (user: IUser) => {
@ -114,11 +116,11 @@ export const SignInView = observer(() => {
const handleGitHubSignIn = async (credential: string) => { const handleGitHubSignIn = async (credential: string) => {
try { try {
setLoading(true); setLoading(true);
if (data && data.github && credential) { if (envConfig && envConfig.github_client_id && credential) {
const socialAuthPayload = { const socialAuthPayload = {
medium: "github", medium: "github",
credential, credential,
clientId: data.github, clientId: envConfig.github_client_id,
}; };
const response = await authService.socialAuth(socialAuthPayload); const response = await authService.socialAuth(socialAuthPayload);
if (response) { if (response) {
@ -195,7 +197,7 @@ export const SignInView = observer(() => {
Sign in to Plane Sign in to Plane
</h1> </h1>
{!data && !appConfigError ? ( {!envConfig ? (
<div className="pt-10 w-ful"> <div className="pt-10 w-ful">
<Loader className="space-y-4 w-full pb-4"> <Loader className="space-y-4 w-full pb-4">
<Loader.Item height="46px" width="360px" /> <Loader.Item height="46px" width="360px" />
@ -211,7 +213,7 @@ export const SignInView = observer(() => {
<> <>
<> <>
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />} {enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
{data?.magic_login && ( {envConfig?.magic_login && (
<div className="flex flex-col divide-y divide-custom-border-200"> <div className="flex flex-col divide-y divide-custom-border-200">
<div className="pb-7"> <div className="pb-7">
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} /> <EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
@ -219,8 +221,12 @@ export const SignInView = observer(() => {
</div> </div>
)} )}
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden"> <div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
{data?.google && <GoogleLoginButton clientId={data?.google} handleSignIn={handleGoogleSignIn} />} {envConfig?.google_client_id && (
{data?.github && <GithubLoginButton clientId={data?.github} handleSignIn={handleGitHubSignIn} />} <GoogleLoginButton clientId={envConfig?.google_client_id} handleSignIn={handleGoogleSignIn} />
)}
{envConfig?.github_client_id && (
<GithubLoginButton clientId={envConfig?.github_client_id} handleSignIn={handleGitHubSignIn} />
)}
</div> </div>
</> </>
<p className="pt-16 text-custom-text-200 text-sm text-center"> <p className="pt-16 text-custom-text-200 text-sm text-center">

View File

@ -1,23 +1,26 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { useRouter } from "next/router"; 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 [authLoader, setAuthLoader] = useState(false);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const providerUrls: { [key: string]: string } = { const providerUrls: { [key: string]: string } = {
github: `https://github.com/apps/${ github: `https://github.com/apps/${github_app_name}/installations/new?state=${workspaceSlug?.toString()}`,
process.env.NEXT_PUBLIC_GITHUB_APP_NAME slack: `https://slack.com/oauth/v2/authorize?scope=chat:write,im:history,im:write,links:read,links:write,users:read,users:read.email&amp;user_scope=&amp;&client_id=${slack_client_id}&state=${workspaceSlug?.toString()}`,
}/installations/new?state=${workspaceSlug?.toString()}`, slackChannel: `https://slack.com/oauth/v2/authorize?scope=incoming-webhook&client_id=${slack_client_id}&state=${workspaceSlug?.toString()},${projectId?.toString()}${
slack: `https://slack.com/oauth/v2/authorize?scope=chat:write,im:history,im:write,links:read,links:write,users:read,users:read.email&amp;user_scope=&amp;&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()}${
stateParams ? "," + stateParams : "" stateParams ? "," + stateParams : ""
}`, }`,
}; };

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
// next themes import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { useRouter } from "next/router";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { useRouter } from "next/router"; // helpers
import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
import { observer } from "mobx-react-lite";
const MobxStoreInit = observer(() => { const MobxStoreInit = observer(() => {
// router // router
@ -13,16 +14,19 @@ const MobxStoreInit = observer(() => {
const { workspaceSlug, projectId, cycleId, moduleId, globalViewId, viewId, inboxId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, globalViewId, viewId, inboxId } = router.query;
// store // store
const { const {
theme: themeStore, theme: { sidebarCollapsed, toggleSidebar },
user: userStore, user: { currentUser },
workspace: workspaceStore, workspace: { setWorkspaceSlug },
project: projectStore, project: { setProjectId },
cycle: cycleStore, cycle: { setCycleId },
module: moduleStore, module: { setModuleId },
globalViews: globalViewsStore, globalViews: { setGlobalViewId },
projectViews: projectViewsStore, projectViews: { setViewId },
inbox: inboxStore, inbox: { setInboxId },
appConfig: { fetchAppConfig },
} = useMobxStore(); } = useMobxStore();
// fetching application Config
useSWR("APP_CONFIG", () => fetchAppConfig(), { revalidateIfStale: false, revalidateOnFocus: false });
// state // state
const [dom, setDom] = useState<any>(); const [dom, setDom] = useState<any>();
// theme // theme
@ -34,36 +38,36 @@ const MobxStoreInit = observer(() => {
useEffect(() => { useEffect(() => {
const localValue = localStorage && localStorage.getItem("app_sidebar_collapsed"); const localValue = localStorage && localStorage.getItem("app_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false; const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
if (localValue && themeStore?.sidebarCollapsed === undefined) { if (localValue && sidebarCollapsed === undefined) {
themeStore.toggleSidebar(localBoolValue); toggleSidebar(localBoolValue);
} }
}, [themeStore, userStore, setTheme]); }, [sidebarCollapsed, currentUser, setTheme, toggleSidebar]);
/** /**
* Setting up the theme of the user by fetching it from local storage * Setting up the theme of the user by fetching it from local storage
*/ */
useEffect(() => { useEffect(() => {
if (!userStore.currentUser) return; if (!currentUser) return;
if (window) { if (window) {
setDom(window.document?.querySelector<HTMLElement>("[data-theme='custom']")); setDom(window.document?.querySelector<HTMLElement>("[data-theme='custom']"));
} }
setTheme(userStore.currentUser?.theme?.theme || "system"); setTheme(currentUser?.theme?.theme || "system");
if (userStore.currentUser?.theme?.theme === "custom" && dom) { if (currentUser?.theme?.theme === "custom" && dom) {
applyTheme(userStore.currentUser?.theme?.palette, false); applyTheme(currentUser?.theme?.palette, false);
} else unsetCustomCssVariables(); } else unsetCustomCssVariables();
}, [userStore.currentUser, setTheme, dom]); }, [currentUser, setTheme, dom]);
/** /**
* Setting router info to the respective stores. * Setting router info to the respective stores.
*/ */
useEffect(() => { useEffect(() => {
if (workspaceSlug) workspaceStore.setWorkspaceSlug(workspaceSlug.toString()); if (workspaceSlug) setWorkspaceSlug(workspaceSlug.toString());
if (projectId) projectStore.setProjectId(projectId.toString()); if (projectId) setProjectId(projectId.toString());
if (cycleId) cycleStore.setCycleId(cycleId.toString()); if (cycleId) setCycleId(cycleId.toString());
if (moduleId) moduleStore.setModuleId(moduleId.toString()); if (moduleId) setModuleId(moduleId.toString());
if (globalViewId) globalViewsStore.setGlobalViewId(globalViewId.toString()); if (globalViewId) setGlobalViewId(globalViewId.toString());
if (viewId) projectViewsStore.setViewId(viewId.toString()); if (viewId) setViewId(viewId.toString());
if (inboxId) inboxStore.setInboxId(inboxId.toString()); if (inboxId) setInboxId(inboxId.toString());
}, [ }, [
workspaceSlug, workspaceSlug,
projectId, projectId,
@ -72,13 +76,13 @@ const MobxStoreInit = observer(() => {
globalViewId, globalViewId,
viewId, viewId,
inboxId, inboxId,
workspaceStore, setWorkspaceSlug,
projectStore, setProjectId,
cycleStore, setCycleId,
moduleStore, setModuleId,
globalViewsStore, setGlobalViewId,
projectViewsStore, setViewId,
inboxStore, setInboxId,
]); ]);
return <></>; return <></>;

View File

@ -2,27 +2,21 @@
import { APIService } from "services/api.service"; import { APIService } from "services/api.service";
// helper // helper
import { API_BASE_URL } from "helpers/common.helper"; import { API_BASE_URL } from "helpers/common.helper";
// types
export interface IEnvConfig { import { IAppConfig } from "types/app";
github: string;
google: string;
github_app_name: string | null;
email_password_login: boolean;
magic_login: boolean;
}
export class AppConfigService extends APIService { export class AppConfigService extends APIService {
constructor() { constructor() {
super(API_BASE_URL); super(API_BASE_URL);
} }
async envConfig(): Promise<IEnvConfig> { async envConfig(): Promise<IAppConfig> {
return this.get("/api/configs/", { return this.get("/api/configs/", {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}) })
.then((response) => response?.data) .then((response) => response.data)
.catch((error) => { .catch((error) => {
throw error?.response?.data; throw error?.response?.data;
}); });

View File

@ -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<any>;
}
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;

View File

@ -1,5 +1,6 @@
import { enableStaticRendering } from "mobx-react-lite"; import { enableStaticRendering } from "mobx-react-lite";
// store imports // store imports
import AppConfigStore, { IAppConfigStore } from "./app-config.store";
import CommandPaletteStore, { ICommandPaletteStore } from "./command-palette.store"; import CommandPaletteStore, { ICommandPaletteStore } from "./command-palette.store";
import UserStore, { IUserStore } from "store/user.store"; import UserStore, { IUserStore } from "store/user.store";
import ThemeStore, { IThemeStore } from "store/theme.store"; import ThemeStore, { IThemeStore } from "store/theme.store";
@ -107,6 +108,7 @@ enableStaticRendering(typeof window === "undefined");
export class RootStore { export class RootStore {
user: IUserStore; user: IUserStore;
theme: IThemeStore; theme: IThemeStore;
appConfig: IAppConfigStore;
commandPalette: ICommandPaletteStore; commandPalette: ICommandPaletteStore;
workspace: IWorkspaceStore; workspace: IWorkspaceStore;
@ -167,6 +169,7 @@ export class RootStore {
mentionsStore: IMentionsStore; mentionsStore: IMentionsStore;
constructor() { constructor() {
this.appConfig = new AppConfigStore(this);
this.commandPalette = new CommandPaletteStore(this); this.commandPalette = new CommandPaletteStore(this);
this.user = new UserStore(this); this.user = new UserStore(this);
this.theme = new ThemeStore(this); this.theme = new ThemeStore(this);

9
web/types/app.d.ts vendored
View File

@ -1,3 +1,12 @@
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & { export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode; 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;
}