diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json
index 9900176e1..8b0487292 100644
--- a/frontend/public/locales/en/translations.json
+++ b/frontend/public/locales/en/translations.json
@@ -336,5 +336,9 @@
"step5-invite-team": "Invite your team",
"step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.",
"step5-skip": "Skip"
+ },
+ "admin": {
+ "signup-title": "Admin Sign Up",
+ "dashboard": "Admin Dashboard"
}
}
diff --git a/frontend/src/components/utilities/checks/OnboardingCheck.ts b/frontend/src/components/utilities/checks/OnboardingCheck.ts
index 01d7a2d55..b3abc9b85 100644
--- a/frontend/src/components/utilities/checks/OnboardingCheck.ts
+++ b/frontend/src/components/utilities/checks/OnboardingCheck.ts
@@ -1,6 +1,7 @@
-import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries";
+import { fetchOrgUsers, fetchUserAction } from "@app/hooks/api/users/queries";
interface OnboardingCheckProps {
+ orgId: string;
setTotalOnboardingActionsDone?: (value: number) => void;
setHasUserClickedSlack?: (value: boolean) => void;
setHasUserClickedIntro?: (value: boolean) => void;
@@ -12,6 +13,7 @@ interface OnboardingCheckProps {
* This function checks which onboarding steps a user has already finished.
*/
const onboardingCheck = async ({
+ orgId,
setTotalOnboardingActionsDone,
setHasUserClickedSlack,
setHasUserClickedIntro,
@@ -19,9 +21,7 @@ const onboardingCheck = async ({
setUsersInOrg
}: OnboardingCheckProps) => {
let countActions = 0;
- const userActionSlack = await fetchUserAction(
- "slack_cta_clicked"
- );
+ const userActionSlack = await fetchUserAction("slack_cta_clicked");
if (userActionSlack) {
countActions += 1;
@@ -41,9 +41,8 @@ const onboardingCheck = async ({
}
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
- const orgId = localStorage.getItem("orgData.id");
const orgUsers = await fetchOrgUsers(orgId || "");
-
+
if (orgUsers.length > 1) {
countActions += 1;
}
diff --git a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx
index f43ee923c..c4759ae01 100644
--- a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx
+++ b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx
@@ -3,13 +3,15 @@
import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
+import { twMerge } from "tailwind-merge";
type Props = {
text?: string | string[];
frequency?: number;
+ className?: string;
};
-export const ContentLoader = ({ text, frequency = 2000 }: Props) => {
+export const ContentLoader = ({ text, frequency = 2000, className }: Props) => {
const [pos, setPos] = useState(0);
const isTextArray = Array.isArray(text);
useEffect(() => {
@@ -23,7 +25,12 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => {
}, []);
return (
-
+

{text && isTextArray && (
diff --git a/frontend/src/components/v2/Switch/Switch.tsx b/frontend/src/components/v2/Switch/Switch.tsx
index c66da2204..a54657fc6 100644
--- a/frontend/src/components/v2/Switch/Switch.tsx
+++ b/frontend/src/components/v2/Switch/Switch.tsx
@@ -3,7 +3,7 @@ import * as SwitchPrimitive from "@radix-ui/react-switch";
import { twMerge } from "tailwind-merge";
export type SwitchProps = Omit & {
- children: ReactNode;
+ children?: ReactNode;
id: string;
isChecked?: boolean;
isRequired?: boolean;
diff --git a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx
index fb1a21d40..1c1d732e1 100644
--- a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx
+++ b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx
@@ -1,7 +1,5 @@
import { useOrganization, useSubscription } from "@app/context";
-import {
- useGetOrgTrialUrl
-} from "@app/hooks/api";
+import { useGetOrgTrialUrl } from "@app/hooks/api";
import { Button } from "../Button";
import { Modal, ModalContent } from "../Modal";
@@ -16,38 +14,36 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
const { subscription } = useSubscription();
const { currentOrg } = useOrganization();
const { mutateAsync, isLoading } = useGetOrgTrialUrl();
- const link = (subscription && subscription.slug !== null)
- ? `/org/${currentOrg?._id}/billing`
- : "https://infisical.com/scheduledemo";
-
+ const link =
+ subscription && subscription.slug !== null
+ ? `/org/${currentOrg?._id}/billing`
+ : "https://infisical.com/scheduledemo";
+
const handleUpgradeBtnClick = async () => {
try {
if (!subscription || !currentOrg) return;
-
+
if (!subscription.has_used_trial) {
// direct user to start pro trial
-
+
const url = await mutateAsync({
orgId: currentOrg._id,
success_url: window.location.href
});
-
+
window.location.href = url;
} else {
// direct user to upgrade their plan
window.location.href = link;
}
-
} catch (err) {
console.error(err);
}
- }
-
+ };
+
return (
-
+
{text}
Upgrade and get access to this, as well as to other powerful enhancements.
@@ -59,10 +55,10 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
onClick={handleUpgradeBtnClick}
className="mr-4"
>
- {(subscription && !subscription.has_used_trial) ? "Start Pro Free Trial" : "Upgrade Plan"}
+ {subscription && !subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
-
- )
-}
\ No newline at end of file
+ );
+};
diff --git a/frontend/src/const.ts b/frontend/src/const.ts
index 267d80ccd..67340780d 100644
--- a/frontend/src/const.ts
+++ b/frontend/src/const.ts
@@ -21,7 +21,8 @@ export const publicPaths = [
"/saml-sso",
"/login/provider/success", // TODO: change
"/login/provider/error", // TODO: change
- "/login/sso"
+ "/login/sso",
+ "/admin/signup"
];
export const languageMap = {
@@ -50,7 +51,8 @@ const plansProd: Mapping = {
export const plans = plansProd || plansDev;
-export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?";
+export const leaveConfirmDefaultMessage =
+ "Your changes will be lost if you leave the page. Are you sure you want to continue?";
export const secretTagsColors = [
{
@@ -115,5 +117,5 @@ export const secretTagsColors = [
rgba: "rgb(255,0,0, 0.8)",
name: "Red",
selected: false
- },
-]
\ No newline at end of file
+ }
+];
diff --git a/frontend/src/context/AuthContext/AuthContext.tsx b/frontend/src/context/AuthContext/AuthContext.tsx
index 7515e459c..3c615485b 100644
--- a/frontend/src/context/AuthContext/AuthContext.tsx
+++ b/frontend/src/context/AuthContext/AuthContext.tsx
@@ -18,7 +18,7 @@ type Props = {
// 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 } = useRouter();
+ const { pathname, push, asPath } = useRouter();
const [isReady, setIsReady] = useToggle(false);
useEffect(() => {
@@ -26,7 +26,7 @@ export const AuthProvider = ({ children }: Props): JSX.Element => {
if (!isLoading) {
// not a public path and not authenticated kick to login page
if (!publicPaths.includes(pathname) && !isLoggedIn()) {
- push("/login").then(() => {
+ push({ pathname: "/login", query: { redirect: asPath } }).then(() => {
setIsReady.on();
});
} else {
@@ -40,7 +40,12 @@ export const AuthProvider = ({ children }: Props): JSX.Element => {
if (isLoading || !isReady) {
return (
-

+
);
}
diff --git a/frontend/src/context/ServerConfigContext/ServerConfigContext.tsx b/frontend/src/context/ServerConfigContext/ServerConfigContext.tsx
new file mode 100644
index 000000000..fba1600cc
--- /dev/null
+++ b/frontend/src/context/ServerConfigContext/ServerConfigContext.tsx
@@ -0,0 +1,53 @@
+import { createContext, ReactNode, useContext, useEffect, useMemo } from "react";
+import { useRouter } from "next/router";
+
+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
(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(() => {
+ return {
+ config: data!
+ };
+ }, [data]);
+
+ useEffect(() => {
+ if (!isLoading && data && !data.initialized) {
+ router.push("/admin/signup");
+ }
+ }, [isLoading, data]);
+
+ if (isLoading || (!data?.initialized && router.pathname !== "/admin/signup")) {
+ return (
+
+
+
+ );
+ }
+
+ return {children};
+};
+
+export const useServerConfig = () => {
+ const ctx = useContext(ServerConfigContext);
+ if (!ctx) {
+ throw new Error("useServerConfig has to be used within ");
+ }
+
+ return ctx;
+};
diff --git a/frontend/src/context/ServerConfigContext/index.tsx b/frontend/src/context/ServerConfigContext/index.tsx
new file mode 100644
index 000000000..aa7e7a4d0
--- /dev/null
+++ b/frontend/src/context/ServerConfigContext/index.tsx
@@ -0,0 +1 @@
+export { ServerConfigProvider,useServerConfig } from "./ServerConfigContext";
diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx
index 35c39f533..11c729080 100644
--- a/frontend/src/context/index.tsx
+++ b/frontend/src/context/index.tsx
@@ -14,6 +14,7 @@ export {
ProjectPermissionSub,
useProjectPermission
} from "./ProjectPermissionContext";
+export { ServerConfigProvider,useServerConfig } from "./ServerConfigContext";
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
export { UserProvider, useUser } from "./UserContext";
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
diff --git a/frontend/src/hooks/api/admin/index.ts b/frontend/src/hooks/api/admin/index.ts
new file mode 100644
index 000000000..e1c4301d4
--- /dev/null
+++ b/frontend/src/hooks/api/admin/index.ts
@@ -0,0 +1,2 @@
+export { useCreateAdminUser, useUpdateServerConfig } from "./mutation";
+export { useGetServerConfig } from "./queries";
diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts
new file mode 100644
index 000000000..ea53ff1db
--- /dev/null
+++ b/frontend/src/hooks/api/admin/mutation.ts
@@ -0,0 +1,39 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+
+import { apiRequest } from "@app/config/request";
+
+import { User } from "../users/types";
+import { adminQueryKeys } from "./queries";
+import { TCreateAdminUserDTO, TServerConfig } from "./types";
+
+export const useCreateAdminUser = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation<{ user: User; token: string }, {}, TCreateAdminUserDTO>({
+ mutationFn: async (opt) => {
+ const { data } = await apiRequest.post("/api/v1/admin/signup", opt);
+ return data;
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries(adminQueryKeys.serverConfig());
+ }
+ });
+};
+
+export const useUpdateServerConfig = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation>({
+ mutationFn: async (opt) => {
+ const { data } = await apiRequest.patch<{ config: TServerConfig }>(
+ "/api/v1/admin/config",
+ opt
+ );
+ return data.config;
+ },
+ onSuccess: (data) => {
+ queryClient.setQueryData(adminQueryKeys.serverConfig(), data);
+ queryClient.invalidateQueries(adminQueryKeys.serverConfig());
+ }
+ });
+};
diff --git a/frontend/src/hooks/api/admin/queries.ts b/frontend/src/hooks/api/admin/queries.ts
new file mode 100644
index 000000000..f64c8bfa9
--- /dev/null
+++ b/frontend/src/hooks/api/admin/queries.ts
@@ -0,0 +1,34 @@
+import { useQuery, UseQueryOptions } from "@tanstack/react-query";
+
+import { apiRequest } from "@app/config/request";
+
+import { TServerConfig } from "./types";
+
+export const adminQueryKeys = {
+ serverConfig: () => ["server-config"] as const
+};
+
+const fetchServerConfig = async () => {
+ const { data } = await apiRequest.get<{ config: TServerConfig }>("/api/v1/admin/config");
+ return data.config;
+};
+
+export const useGetServerConfig = ({
+ options = {}
+}: {
+ options?: Omit<
+ UseQueryOptions<
+ TServerConfig,
+ unknown,
+ TServerConfig,
+ ReturnType
+ >,
+ "queryKey" | "queryFn"
+ >;
+} = {}) =>
+ useQuery({
+ queryKey: adminQueryKeys.serverConfig(),
+ queryFn: fetchServerConfig,
+ ...options,
+ enabled: options?.enabled ?? true
+ });
diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts
new file mode 100644
index 000000000..72453ad44
--- /dev/null
+++ b/frontend/src/hooks/api/admin/types.ts
@@ -0,0 +1,19 @@
+export type TServerConfig = {
+ initialized: boolean;
+ allowSignUp: boolean;
+};
+
+export type TCreateAdminUserDTO = {
+ email: string;
+ firstName: string;
+ lastName?: string;
+ protectedKey: string;
+ protectedKeyTag: string;
+ protectedKeyIV: string;
+ encryptedPrivateKey: string;
+ encryptedPrivateKeyIV: string;
+ encryptedPrivateKeyTag: string;
+ publicKey: string;
+ verifier: string;
+ salt: string;
+};
diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx
index ea5bb658d..c5e719218 100644
--- a/frontend/src/hooks/api/index.tsx
+++ b/frontend/src/hooks/api/index.tsx
@@ -1,4 +1,5 @@
export * from "./apiKeys";
+export * from "./admin"
export * from "./auditLogs";
export * from "./auth";
export * from "./bots";
diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts
index c6b790592..3b57e8953 100644
--- a/frontend/src/hooks/api/users/types.ts
+++ b/frontend/src/hooks/api/users/types.ts
@@ -14,6 +14,7 @@ export type User = {
createdAt: Date;
updatedAt: Date;
email: string;
+ superAdmin: boolean;
firstName?: string;
lastName?: string;
authProvider?: AuthMethod;
diff --git a/frontend/src/layouts/AdminLayout/AdminLayout.tsx b/frontend/src/layouts/AdminLayout/AdminLayout.tsx
new file mode 100644
index 000000000..ad156d748
--- /dev/null
+++ b/frontend/src/layouts/AdminLayout/AdminLayout.tsx
@@ -0,0 +1,306 @@
+/* eslint-disable no-nested-ternary */
+/* eslint-disable no-unexpected-multiline */
+/* eslint-disable react-hooks/exhaustive-deps */
+/* eslint-disable vars-on-top */
+/* eslint-disable no-var */
+/* eslint-disable func-names */
+// @ts-nocheck
+
+import { useTranslation } from "react-i18next";
+import Image from "next/image";
+import Link from "next/link";
+import { useRouter } from "next/router";
+import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
+import {
+ faArrowLeft,
+ faArrowUpRightFromSquare,
+ faBook,
+ faEnvelope,
+ faInfinity,
+ faInfo,
+ faMobile,
+ faPlus,
+ faQuestion
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
+
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@app/components/v2";
+import { useOrganization, useSubscription, useUser } from "@app/context";
+import {
+ useGetOrgTrialUrl,
+ useGetUserAction,
+ useLogoutUser,
+ useRegisterUserAction
+} from "@app/hooks/api";
+
+interface LayoutProps {
+ children: React.ReactNode;
+}
+
+const supportOptions = [
+ [
+ ,
+ "Support Forum",
+ "https://infisical.com/slack"
+ ],
+ [
+ ,
+ "Read Docs",
+ "https://infisical.com/docs/documentation/getting-started/introduction"
+ ],
+ [
+ ,
+ "GitHub Issues",
+ "https://github.com/Infisical/infisical/issues"
+ ],
+ [
+ ,
+ "Email Support",
+ "mailto:support@infisical.com"
+ ]
+];
+
+export const AdminLayout = ({ children }: LayoutProps) => {
+ const router = useRouter();
+ const { mutateAsync } = useGetOrgTrialUrl();
+
+ // eslint-disable-next-line prefer-const
+ const { currentOrg } = useOrganization();
+
+ const { user } = useUser();
+ const { subscription } = useSubscription();
+ const { data: updateClosed } = useGetUserAction("september_update_closed");
+ const infisicalPlatformVersion = process.env.NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION;
+
+ const { t } = useTranslation();
+
+ const registerUserAction = useRegisterUserAction();
+
+ const closeUpdate = async () => {
+ await registerUserAction.mutateAsync("september_update_closed");
+ };
+
+ const logout = useLogoutUser();
+ const logOutUser = async () => {
+ try {
+ console.log("Logging out...");
+ await logout.mutateAsync();
+ router.push("/login");
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+ {children}
+
+
+
+
+
+
+ {` ${t("common.no-mobile")} `}
+
+
+ >
+ );
+};
diff --git a/frontend/src/layouts/AdminLayout/index.tsx b/frontend/src/layouts/AdminLayout/index.tsx
new file mode 100644
index 000000000..1a136d62f
--- /dev/null
+++ b/frontend/src/layouts/AdminLayout/index.tsx
@@ -0,0 +1 @@
+export { AdminLayout } from "./AdminLayout";
diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx
index 0757196c9..5b19b657c 100644
--- a/frontend/src/layouts/AppLayout/AppLayout.tsx
+++ b/frontend/src/layouts/AppLayout/AppLayout.tsx
@@ -399,6 +399,13 @@ export const AppLayout = ({ children }: LayoutProps) => {
/>
+ {user?.superAdmin && (
+
+
+ Admin Panel
+
+
+ )}
{!isLoading && loginError && }
- {!serverDetails?.inviteOnlySignup ? (
+ {config.allowSignUp ? (
@@ -236,7 +236,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
) : (
)}
-
+
Forgot password? Recover your account
diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx
new file mode 100644
index 000000000..6099a4d91
--- /dev/null
+++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx
@@ -0,0 +1,64 @@
+import { useEffect } from "react";
+import { useRouter } from "next/router";
+
+import { ContentLoader, Switch, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
+import { useOrganization, useServerConfig, useUser } from "@app/context";
+import { useUpdateServerConfig } from "@app/hooks/api";
+
+enum TabSections {
+ Settings = "settings"
+}
+
+export const AdminDashboardPage = () => {
+ const router = useRouter();
+ const data = useServerConfig();
+ const { config } = data;
+ const { user, isLoading: isUserLoading } = useUser();
+ const { orgs } = useOrganization();
+ const { mutate: updateServerConfig } = useUpdateServerConfig();
+
+ const isNotAllowed = !user?.superAdmin;
+
+ useEffect(() => {
+ if (isNotAllowed && !isUserLoading) {
+ if (orgs?.length) {
+ localStorage.setItem("orgData.id", orgs?.[0]?._id);
+ router.push(`/org/${orgs?.[0]?._id}/overview`);
+ }
+ }
+ }, [isNotAllowed, isUserLoading]);
+
+ return (
+
+
+
+
Admin Dashboard
+
Manage your Infisical.
+
+
+ {isUserLoading || isNotAllowed ? (
+
+ ) : (
+
+
+
+
+ General
+
+
+
+
+
updateServerConfig({ allowSignUp: isChecked })}
+ />
+ Enable signup or invite
+
+
+
+
+ )}
+
+ );
+};
diff --git a/frontend/src/views/admin/DashboardPage/index.tsx b/frontend/src/views/admin/DashboardPage/index.tsx
new file mode 100644
index 000000000..1d5f062e6
--- /dev/null
+++ b/frontend/src/views/admin/DashboardPage/index.tsx
@@ -0,0 +1 @@
+export { AdminDashboardPage } from "./DashboardPage";
diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx
new file mode 100644
index 000000000..544204b13
--- /dev/null
+++ b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx
@@ -0,0 +1,164 @@
+import { useEffect } from "react";
+import { Controller, useForm } from "react-hook-form";
+import { useRouter } from "next/router";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+
+import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
+// TODO(akhilmhdh): rewrite this into module functions in lib
+import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
+import SecurityClient from "@app/components/utilities/SecurityClient";
+import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
+import { useServerConfig } from "@app/context";
+import { useCreateAdminUser } from "@app/hooks/api";
+import { generateUserPassKey } from "@app/lib/crypto";
+import { isLoggedIn } from "@app/reactQuery";
+
+const formSchema = z
+ .object({
+ email: z.string().email().trim(),
+ firstName: z.string().trim(),
+ lastName: z.string().trim().optional(),
+ password: z.string().trim().min(14).max(100),
+ confirmPassword: z.string().trim()
+ })
+ .refine((data) => data.password === data.confirmPassword, {
+ message: "Password don't match",
+ path: ["confirmPassword"]
+ });
+
+type TFormSchema = z.infer;
+
+export const SignUpPage = () => {
+ const router = useRouter();
+ const {
+ control,
+ handleSubmit,
+ formState: { isSubmitting }
+ } = useForm({
+ resolver: zodResolver(formSchema)
+ });
+ const { createNotification } = useNotificationContext();
+
+ const { config } = useServerConfig();
+
+ useEffect(() => {
+ if (config?.initialized) {
+ if (isLoggedIn()) {
+ router.push("/admin");
+ } else {
+ router.push("/login");
+ }
+ }
+ }, [config.initialized]);
+
+ const { mutateAsync: createAdminUser } = useCreateAdminUser();
+
+ const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => {
+ // avoid multi submission
+ if (isSubmitting) return;
+ try {
+ const { privateKey, ...userPass } = await generateUserPassKey(email, password);
+ const res = await createAdminUser({
+ email,
+ firstName,
+ lastName,
+ ...userPass
+ });
+ SecurityClient.setToken(res.token);
+ saveTokenToLocalStorage({
+ publicKey: userPass.publicKey,
+ encryptedPrivateKey: userPass.encryptedPrivateKey,
+ iv: userPass.encryptedPrivateKeyIV,
+ tag: userPass.encryptedPrivateKeyTag,
+ privateKey
+ });
+ } catch (err) {
+ console.log(err);
+ createNotification({
+ type: "error",
+ text: "Faield to create admin"
+ });
+ }
+ };
+
+ if (config?.initialized) return ;
+
+ return (
+
+
+
+

+
Welcome to Infisical
+
Create your first Admin Account
+
+
+
+
+ );
+};
diff --git a/frontend/src/views/admin/SignUpPage/index.tsx b/frontend/src/views/admin/SignUpPage/index.tsx
new file mode 100644
index 000000000..aed0b2d78
--- /dev/null
+++ b/frontend/src/views/admin/SignUpPage/index.tsx
@@ -0,0 +1 @@
+export { SignUpPage } from "./SignUpPage";