mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 15:41:27 +00:00
feat: renamed old frontend and fixed some minor bugs
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { publicPaths } from "@app/const";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useGetAuthToken } from "@app/hooks/api";
|
||||
import { isLoggedIn } from "@app/reactQuery";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
// TODO(akhilmhdh): Using react-simple-animate from hard dom offloading
|
||||
// smoother dom offloading needs to be done
|
||||
|
||||
// Authentication controller
|
||||
// Does route checking
|
||||
// Provide a context for whole app to notify user is authorized or not
|
||||
export const AuthProvider = ({ children }: Props): JSX.Element => {
|
||||
const { isLoading } = useGetAuthToken();
|
||||
const { pathname, push, asPath } = useRouter();
|
||||
const [isReady, setIsReady] = useToggle(false);
|
||||
|
||||
useEffect(() => {
|
||||
// check if loading of auth is done
|
||||
if (!isLoading) {
|
||||
// not a public path and not authenticated kick to login page
|
||||
if (!publicPaths.includes(pathname) && !isLoggedIn()) {
|
||||
push({ pathname: "/login", query: { redirect: asPath } }).then(() => {
|
||||
setIsReady.on();
|
||||
});
|
||||
} else {
|
||||
// else good to go
|
||||
setIsReady.on();
|
||||
}
|
||||
}
|
||||
}, [pathname, isLoading]);
|
||||
|
||||
// wait for app to load the auth state
|
||||
if (isLoading || !isReady) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||
<img
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return children as JSX.Element;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { AuthProvider } from "./AuthContext";
|
||||
@@ -1,58 +1,32 @@
|
||||
import { createContext, ReactNode, useContext } from "react";
|
||||
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { unpackRules } from "@casl/ability/extra";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useRouteContext } from "@tanstack/react-router";
|
||||
|
||||
import { useGetUserOrgPermissions } from "@app/hooks/api";
|
||||
import { OrgUser } from "@app/hooks/api/types";
|
||||
import {
|
||||
conditionsMatcher,
|
||||
fetchUserOrgPermissions,
|
||||
roleQueryKeys
|
||||
} from "@app/hooks/api/roles/queries";
|
||||
|
||||
import { useOrganization } from "../OrganizationContext";
|
||||
import { TOrgPermission } from "./types";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const OrgPermissionContext = createContext<null | {
|
||||
permission: TOrgPermission;
|
||||
membership: OrgUser | null;
|
||||
}>(null);
|
||||
|
||||
export const OrgPermissionProvider = ({ children }: Props): JSX.Element => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { data: permission, isLoading } = useGetUserOrgPermissions({ orgId });
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||
<img
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!permission) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||
Failed to load user permissions
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OrgPermissionContext.Provider value={permission}>{children}</OrgPermissionContext.Provider>
|
||||
);
|
||||
};
|
||||
import { OrgPermissionSet } from "./types";
|
||||
|
||||
export const useOrgPermission = () => {
|
||||
const ctx = useContext(OrgPermissionContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useOrgPermission to be used within <OrgPermissionProvider>");
|
||||
}
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
});
|
||||
|
||||
return ctx;
|
||||
const { data } = useSuspenseQuery({
|
||||
queryKey: roleQueryKeys.getUserOrgPermissions({ orgId: organizationId }),
|
||||
queryFn: () => fetchUserOrgPermissions({ orgId: organizationId }),
|
||||
select: (res) => {
|
||||
const rule = unpackRules<RawRuleOf<MongoAbility<OrgPermissionSet>>>(res.permissions);
|
||||
const ability = createMongoAbility<OrgPermissionSet>(rule, { conditionsMatcher });
|
||||
return { permission: ability, membership: res.membership };
|
||||
},
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { OrgPermissionProvider, useOrgPermission } from "./OrgPermissionContext";
|
||||
export { useOrgPermission } from "./OrgPermissionContext";
|
||||
export type { TOrgPermission } from "./types";
|
||||
export { OrgPermissionActions, OrgPermissionSubjects } from "./types";
|
||||
|
||||
@@ -1,44 +1,19 @@
|
||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useRouteContext } from "@tanstack/react-router";
|
||||
|
||||
import { useGetOrganizations } from "@app/hooks/api";
|
||||
import { Organization } from "@app/hooks/api/types";
|
||||
|
||||
type TOrgContext = {
|
||||
orgs?: Organization[];
|
||||
currentOrg?: Organization;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const OrgContext = createContext<TOrgContext | null>(null);
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const OrgProvider = ({ children }: Props): JSX.Element => {
|
||||
const { data: userOrgs, isLoading } = useGetOrganizations();
|
||||
|
||||
// const currentWsOrgID = currentWorkspace?.organization;
|
||||
const currentWsOrgID = localStorage.getItem("orgData.id");
|
||||
|
||||
// memorize the workspace details for the context
|
||||
const value = useMemo<TOrgContext>(
|
||||
() => ({
|
||||
orgs: userOrgs,
|
||||
currentOrg: (userOrgs || []).find(({ id }) => id === currentWsOrgID),
|
||||
isLoading
|
||||
}),
|
||||
[currentWsOrgID, userOrgs, isLoading]
|
||||
);
|
||||
|
||||
return <OrgContext.Provider value={value}>{children}</OrgContext.Provider>;
|
||||
};
|
||||
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
||||
|
||||
export const useOrganization = () => {
|
||||
const ctx = useContext(OrgContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useOrganization to be used within <OrgContext.Provider>");
|
||||
}
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
});
|
||||
|
||||
return ctx;
|
||||
const { data: currentOrg } = useSuspenseQuery({
|
||||
queryKey: organizationKeys.getOrgById(organizationId),
|
||||
queryFn: () => fetchOrganizationById(organizationId),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return { currentOrg };
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgProvider, useOrganization } from "./OrganizationContext";
|
||||
export { useOrganization } from "./OrganizationContext";
|
||||
|
||||
@@ -1,62 +1,77 @@
|
||||
import { createContext, ReactNode, useContext } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { unpackRules } from "@casl/ability/extra";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
|
||||
import { useGetUserProjectPermissions } from "@app/hooks/api";
|
||||
import { TProjectMembership } from "@app/hooks/api/users/types";
|
||||
import {
|
||||
conditionsMatcher,
|
||||
fetchUserProjectPermissions,
|
||||
roleQueryKeys
|
||||
} from "@app/hooks/api/roles/queries";
|
||||
import { groupBy } from "@app/lib/fn/array";
|
||||
import { omit } from "@app/lib/fn/object";
|
||||
|
||||
import { useWorkspace } from "../WorkspaceContext";
|
||||
import { TProjectPermission } from "./types";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ProjectPermissionContext = createContext<null | {
|
||||
permission: TProjectPermission;
|
||||
membership: TProjectMembership;
|
||||
}>(null);
|
||||
|
||||
export const ProjectPermissionProvider = ({ children }: Props): JSX.Element => {
|
||||
const { currentWorkspace, isLoading: isWsLoading } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const { data: permission, isLoading } = useGetUserProjectPermissions({ workspaceId });
|
||||
|
||||
if ((isLoading && currentWorkspace) || isWsLoading) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||
<img
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!permission && currentWorkspace) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||
Failed to load user permissions
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectPermissionContext.Provider value={permission!}>
|
||||
{children}
|
||||
</ProjectPermissionContext.Provider>
|
||||
);
|
||||
};
|
||||
import { ProjectPermissionSet } from "./types";
|
||||
|
||||
export const useProjectPermission = () => {
|
||||
const ctx = useContext(ProjectPermissionContext);
|
||||
if (!ctx) {
|
||||
const projectId = useParams({
|
||||
strict: false,
|
||||
select: (el) => el?.projectId
|
||||
});
|
||||
if (!projectId) {
|
||||
throw new Error("useProjectPermission to be used within <ProjectPermissionContext>");
|
||||
}
|
||||
|
||||
const hasProjectRole = (role: string) => ctx?.membership?.roles?.includes(role) || false;
|
||||
const {
|
||||
data: { permission, membership }
|
||||
} = useSuspenseQuery({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({ workspaceId: projectId }),
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: projectId }),
|
||||
staleTime: Infinity,
|
||||
select: (data) => {
|
||||
const rule = unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(data.permissions);
|
||||
const negatedRules = groupBy(
|
||||
rule.filter((i) => i.inverted && i.conditions),
|
||||
(i) => `${i.subject}-${JSON.stringify(i.conditions)}`
|
||||
);
|
||||
const ability = createMongoAbility<ProjectPermissionSet>(rule, {
|
||||
// this allows in frontend to skip some rules using *
|
||||
conditionsMatcher: (rules) => {
|
||||
return (entity) => {
|
||||
// skip validation if its negated rules
|
||||
const isNegatedRule =
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
negatedRules?.[`${entity.__caslSubjectType__}-${JSON.stringify(rules)}`];
|
||||
if (isNegatedRule) {
|
||||
const baseMatcher = conditionsMatcher(rules);
|
||||
return baseMatcher(entity);
|
||||
}
|
||||
|
||||
return { ...ctx, hasProjectRole };
|
||||
const rulesStrippedOfWildcard = omit(
|
||||
rules,
|
||||
Object.keys(entity).filter((el) => entity[el]?.includes("*"))
|
||||
);
|
||||
const baseMatcher = conditionsMatcher(rulesStrippedOfWildcard);
|
||||
return baseMatcher(entity);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
permission: ability,
|
||||
membership: {
|
||||
...data.membership,
|
||||
roles: data.membership.roles.map(({ role }) => role)
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const hasProjectRole = useCallback(
|
||||
(role: string) => membership?.roles?.includes(role) || false,
|
||||
[]
|
||||
);
|
||||
|
||||
return { permission, membership, hasProjectRole };
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { ProjectPermissionProvider, useProjectPermission } from "./ProjectPermissionContext";
|
||||
export { useProjectPermission } from "./ProjectPermissionContext";
|
||||
export type { ProjectPermissionSet, TProjectPermission } from "./types";
|
||||
export {
|
||||
ProjectPermissionActions,
|
||||
|
||||
@@ -1,89 +1,13 @@
|
||||
import { createContext, ReactNode, useContext, useEffect, useMemo } from "react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ContentLoader } from "@app/components/v2/ContentLoader";
|
||||
import { useGetServerConfig } from "@app/hooks/api";
|
||||
import { TServerConfig } from "@app/hooks/api/admin/types";
|
||||
|
||||
type TServerConfigContext = {
|
||||
config: TServerConfig;
|
||||
};
|
||||
|
||||
const ServerConfigContext = createContext<TServerConfigContext | null>(null);
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ServerConfigProvider = ({ children }: Props): JSX.Element => {
|
||||
const router = useRouter();
|
||||
const { data, isLoading } = useGetServerConfig();
|
||||
|
||||
// memorize the workspace details for the context
|
||||
const value = useMemo<TServerConfigContext>(() => {
|
||||
return {
|
||||
config: data!
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && data && !data.initialized && !data.isMigrationModeOn) {
|
||||
router.push("/admin/signup");
|
||||
}
|
||||
}, [isLoading, data]);
|
||||
|
||||
if (!isLoading && data?.isMigrationModeOn) {
|
||||
return (
|
||||
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center space-y-8 bg-bunker-800 px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||
<Head>
|
||||
<title>Infisical Maintenance Mode</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<img
|
||||
src="/images/maintenance.png"
|
||||
height={175}
|
||||
width={300}
|
||||
alt="maintenance mode"
|
||||
className="w-[40rem]"
|
||||
/>
|
||||
<p className="mx-8 mb-4 flex justify-center bg-gradient-to-tr from-mineshaft-300 to-white bg-clip-text text-4xl font-bold text-transparent md:mx-16">
|
||||
Scheduled Maintenance
|
||||
</p>
|
||||
<div className="mt-2 text-center text-lg text-bunker-300">
|
||||
Infisical is undergoing planned maintenance. <br /> No action is required on your end —
|
||||
your applications will continue to fetch secrets.
|
||||
<br /> If you have questions, please{" "}
|
||||
<a
|
||||
className="text-bunker-300 underline decoration-primary-800 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
|
||||
href="https://infisical.com/slack"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
join our Slack community
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading || (!data?.initialized && router.pathname !== "/admin/signup")) {
|
||||
return (
|
||||
<div className="bg-bunker-800">
|
||||
<ContentLoader text="Loading configurations" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <ServerConfigContext.Provider value={value}>{children}</ServerConfigContext.Provider>;
|
||||
};
|
||||
import { adminQueryKeys, fetchServerConfig } from "@app/hooks/api/admin/queries";
|
||||
|
||||
export const useServerConfig = () => {
|
||||
const ctx = useContext(ServerConfigContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useServerConfig has to be used within <UserContext.Provider>");
|
||||
}
|
||||
const { data: config } = useSuspenseQuery({
|
||||
queryKey: adminQueryKeys.serverConfig(),
|
||||
queryFn: fetchServerConfig,
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return ctx;
|
||||
return { config };
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ServerConfigProvider, useServerConfig } from "./ServerConfigContext";
|
||||
export { useServerConfig } from "./ServerConfigContext";
|
||||
|
||||
@@ -1,45 +1,19 @@
|
||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useRouteContext } from "@tanstack/react-router";
|
||||
|
||||
import { useGetOrgSubscription } from "@app/hooks/api";
|
||||
import { SubscriptionPlan } from "@app/hooks/api/types";
|
||||
|
||||
import { useOrganization } from "../OrganizationContext";
|
||||
|
||||
type TSubscriptionContext = {
|
||||
subscription?: SubscriptionPlan;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const SubscriptionContext = createContext<TSubscriptionContext | null>(null);
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const SubscriptionProvider = ({ children }: Props): JSX.Element => {
|
||||
const { currentOrg } = useOrganization();
|
||||
|
||||
const { data, isLoading } = useGetOrgSubscription({
|
||||
orgID: currentOrg?.id || ""
|
||||
});
|
||||
|
||||
// memorize the workspace details for the context
|
||||
const value = useMemo<TSubscriptionContext>(
|
||||
() => ({
|
||||
subscription: data,
|
||||
isLoading
|
||||
}),
|
||||
[data, isLoading]
|
||||
);
|
||||
|
||||
return <SubscriptionContext.Provider value={value}>{children}</SubscriptionContext.Provider>;
|
||||
};
|
||||
import { fetchOrgSubscription, subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||
|
||||
export const useSubscription = () => {
|
||||
const ctx = useContext(SubscriptionContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useSubscription has to be used within <SubscriptionContext.Provider>");
|
||||
}
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
});
|
||||
|
||||
return ctx;
|
||||
const { data: subscription } = useSuspenseQuery({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId),
|
||||
queryFn: () => fetchOrgSubscription(organizationId),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return { subscription };
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
|
||||
export { useSubscription } from "./SubscriptionContext";
|
||||
|
||||
@@ -1,53 +1,14 @@
|
||||
import { createContext, ReactNode, useContext, useMemo } from "react";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
|
||||
import { useGetUser } from "@app/hooks/api";
|
||||
import { User, UserEnc } from "@app/hooks/api/types";
|
||||
|
||||
type TUserContext = {
|
||||
user: User & UserEnc;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const UserContext = createContext<TUserContext | null>(null);
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const UserProvider = ({ children }: Props): JSX.Element => {
|
||||
const { data, isLoading } = useGetUser();
|
||||
|
||||
// memorize the workspace details for the context
|
||||
const value = useMemo<TUserContext>(() => {
|
||||
return {
|
||||
user: data!,
|
||||
isLoading
|
||||
};
|
||||
}, [data, isLoading]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||
<img
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
alt="infisical loading indicator"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
|
||||
};
|
||||
import { userKeys } from "@app/hooks/api";
|
||||
import { fetchUserDetails } from "@app/hooks/api/users/queries";
|
||||
|
||||
export const useUser = () => {
|
||||
const ctx = useContext(UserContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useUser has to be used within <UserContext.Provider>");
|
||||
}
|
||||
const { data: user } = useSuspenseQuery({
|
||||
queryKey: userKeys.getUser,
|
||||
queryFn: fetchUserDetails,
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return ctx;
|
||||
return { user };
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { UserProvider, useUser } from "./UserContext";
|
||||
export { useUser } from "./UserContext";
|
||||
|
||||
@@ -1,73 +1,22 @@
|
||||
import { createContext, ReactNode, useContext, useEffect, useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { useGetUserWorkspaces } from "@app/hooks/api";
|
||||
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
|
||||
|
||||
type TWorkspaceContext = {
|
||||
workspaces: Workspace[];
|
||||
currentWorkspace?: Workspace;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const WorkspaceContext = createContext<TWorkspaceContext | null>(null);
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
|
||||
const { data: ws, isLoading } = useGetUserWorkspaces();
|
||||
const router = useRouter();
|
||||
const workspaceId = router.query.id;
|
||||
|
||||
// memorize the workspace details for the context
|
||||
const value = useMemo<TWorkspaceContext>(() => {
|
||||
const wsId = workspaceId || localStorage.getItem("projectData.id");
|
||||
return {
|
||||
workspaces: ws || [],
|
||||
currentWorkspace: (ws || []).find(({ id }) => id === wsId),
|
||||
isLoading
|
||||
};
|
||||
}, [ws, workspaceId, isLoading]);
|
||||
|
||||
const shouldTriggerNoProjectAccess =
|
||||
!value.isLoading &&
|
||||
!value.currentWorkspace &&
|
||||
Object.values(ProjectType).some((el) => router.pathname.startsWith(`/${el}`)) &&
|
||||
workspaceId;
|
||||
|
||||
// handle redirects for project-specific routes
|
||||
useEffect(() => {
|
||||
if (shouldTriggerNoProjectAccess) {
|
||||
createNotification({
|
||||
text: "You are not a member of this project.",
|
||||
type: "info"
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
router.push("/");
|
||||
}, 5000);
|
||||
}
|
||||
}, [shouldTriggerNoProjectAccess, router]);
|
||||
|
||||
if (shouldTriggerNoProjectAccess) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800 text-primary-50">
|
||||
You do not have sufficient access to this project.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <WorkspaceContext.Provider value={value}>{children}</WorkspaceContext.Provider>;
|
||||
};
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
|
||||
export const useWorkspace = () => {
|
||||
const ctx = useContext(WorkspaceContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useWorkspace has to be used within <WorkspaceContext.Provider>");
|
||||
const params = useParams({
|
||||
strict: false
|
||||
});
|
||||
if (!params.projectId) {
|
||||
throw new Error("Missing project id");
|
||||
}
|
||||
|
||||
return ctx;
|
||||
const { data: currentWorkspace } = useSuspenseQuery({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId as string),
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return { currentWorkspace };
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
|
||||
export { useWorkspace } from "./WorkspaceContext";
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
export { AuthProvider } from "./AuthContext";
|
||||
export { OrgProvider, useOrganization } from "./OrganizationContext";
|
||||
export { useOrganization } from "./OrganizationContext";
|
||||
export type { TOrgPermission } from "./OrgPermissionContext";
|
||||
export {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionProvider,
|
||||
OrgPermissionSubjects,
|
||||
useOrgPermission
|
||||
} from "./OrgPermissionContext";
|
||||
@@ -12,11 +10,10 @@ export {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionCmekActions,
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionProvider,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission
|
||||
} from "./ProjectPermissionContext";
|
||||
export { ServerConfigProvider, useServerConfig } from "./ServerConfigContext";
|
||||
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
|
||||
export { UserProvider, useUser } from "./UserContext";
|
||||
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
|
||||
export { useServerConfig } from "./ServerConfigContext";
|
||||
export { useSubscription } from "./SubscriptionContext";
|
||||
export { useUser } from "./UserContext";
|
||||
export { useWorkspace } from "./WorkspaceContext";
|
||||
|
||||
Reference in New Issue
Block a user