feat: renamed old frontend and fixed some minor bugs

This commit is contained in:
=
2025-01-06 14:56:49 +05:30
parent 8c902d7699
commit 54377a44d3
2577 changed files with 35240 additions and 35116 deletions
@@ -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
View File
@@ -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";
+5 -8
View File
@@ -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";