diff --git a/frontend/src/components/v2/Input/Input.tsx b/frontend/src/components/v2/Input/Input.tsx index 9263a4aad..5dd11642d 100644 --- a/frontend/src/components/v2/Input/Input.tsx +++ b/frontend/src/components/v2/Input/Input.tsx @@ -13,7 +13,7 @@ type Props = { }; const inputVariants = cva( - "input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2 hover:ring-[0.05rem] hover:ring-bunker-400/60 duration-100", + "input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2 hover:ring-bunker-400/60 duration-100", { variants: { size: { diff --git a/frontend/src/hooks/useSyntaxHighlight.tsx b/frontend/src/hooks/useSyntaxHighlight.tsx index 325917c83..16ce3d46c 100644 --- a/frontend/src/hooks/useSyntaxHighlight.tsx +++ b/frontend/src/hooks/useSyntaxHighlight.tsx @@ -7,6 +7,10 @@ const REGEX = /([$]{.*?})/g; export const useSyntaxHighlight = () => { const syntaxHighlight = useCallback((text: string, isHidden?: boolean) => { if (isHidden) { + if (!text) { + return "EMPTY"; + } + return text .split("") .slice(0, 200) diff --git a/frontend/src/layouts/AppLayout/components/NavBar/NavBar.tsx b/frontend/src/layouts/AppLayout/components/NavBar/NavBar.tsx new file mode 100644 index 000000000..b263a25ff --- /dev/null +++ b/frontend/src/layouts/AppLayout/components/NavBar/NavBar.tsx @@ -0,0 +1,359 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable react/jsx-key */ +import { Fragment, useMemo } from "react"; +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 { faCircleQuestion } from "@fortawesome/free-regular-svg-icons"; +import { + faAngleDown, + faBook, + faCoins, + faEnvelope, + faGear, + faPlus, + faRightFromBracket +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Menu, Transition } from "@headlessui/react"; +import {TFunction} from "i18next"; + +import guidGenerator from "@app/components/utilities/randomId"; +import { useOrganization, useSubscription,useUser } from "@app/context"; +import { useLogoutUser } from "@app/hooks/api"; + +const supportOptions = (t: TFunction) => [ + [ + , + t("nav.support.slack"), + "https://infisical.com/slack" + ], + [ + , + t("nav.support.docs"), + "https://infisical.com/docs/documentation/getting-started/introduction" + ], + [ + , + t("nav.support.issue"), + "https://github.com/Infisical/infisical-cli/issues" + ], + [ + , + t("nav.support.email"), + "mailto:support@infisical.com" + ] +]; + +export interface ICurrentOrg { + name: string; +} + +export interface IUser { + firstName: string; + lastName: string; + email: string; +} + +/** + * This is the navigation bar in the main app. + * It has two main components: support options and user menu (inlcudes billing, logout, org/user settings) + * @returns NavBar + */ +export const Navbar = () => { + const router = useRouter(); + const { subscription } = useSubscription(); + + const { currentOrg, orgs } = useOrganization(); + const { user } = useUser(); + + const logout = useLogoutUser(); + + const { t } = useTranslation(); + + // remove this memo + const supportOptionsList = useMemo(() => supportOptions(t), [t]); + + const closeApp = async () => { + try { + console.log("Logging out...") + await logout.mutateAsync(); + localStorage.removeItem("protectedKey"); + localStorage.removeItem("protectedKeyIV"); + localStorage.removeItem("protectedKeyTag"); + localStorage.removeItem("publicKey"); + localStorage.removeItem("encryptedPrivateKey"); + localStorage.removeItem("iv"); + localStorage.removeItem("tag"); + localStorage.removeItem("PRIVATE_KEY"); + localStorage.removeItem("orgData.id"); + localStorage.removeItem("projectData.id"); + router.push("/login"); + } catch (error) { + console.error(error); + } + }; + + function formatPlanSlug(slug: string) { + return slug + .replace(/(\b[a-z])/g, match => match.toUpperCase()) + .replace(/-/g, " "); + } + + const calculateRemainingDays = (date: number) => { + const now = new Date(); + const endDate = new Date(date * 1000); + + const differenceInTime = endDate.getTime() - now.getTime(); + const differenceInDays = Math.ceil(differenceInTime / (1000 * 3600 * 24)); + + return differenceInDays; + } + + const formatDate = (date: number) => { + const endDate = new Date(date * 1000); + const day: number = endDate.getDate(); + const month: number = endDate.getMonth() + 1; + const year: number = endDate.getFullYear(); + + const formattedDate: string = `${day}/${month}/${year}`; + const remainingDays: number = calculateRemainingDays(date); + + return { + formattedDate, + remainingDays + }; + } + + return ( +
+
+
+
+ logo +
+ + Infisical + +
+
+ + + Docs + + +
+ + + +
+ + + {supportOptionsList.map(([icon, text, url]) => ( + +
+ {icon} +
{text}
+
+
+ ))} +
+
+
+ +
+ + {user?.firstName} {user?.lastName} + + +
+ + +
+
+ {t("nav.user.signed-in-as")} +
+
null} + role="button" + tabIndex={0} + onClick={() => router.push(`/settings/personal/${router.query.id}`)} + className="mx-1 my-1 flex cursor-pointer flex-row items-center rounded-md px-1 hover:bg-white/5" + > +
+ {user?.firstName?.charAt(0)} +
+
+
+

+ {" "} + {user?.firstName} {user?.lastName} +

+

{user?.email}

+
+ +
+
+
+
+
+ {t("nav.user.current-organization")} +
+
null} + role="button" + tabIndex={0} + onClick={() => router.push(`/settings/org/${router.query.id}`)} + className="mt-2 flex cursor-pointer flex-row items-center rounded-md px-2 py-1 hover:bg-white/5" + > +
+ {currentOrg?.name?.charAt(0)} +
+
+

{currentOrg?.name}

+ +
+
+ {subscription && subscription.slug !== null && ( + + )} + +
+ {orgs && orgs?.length > 1 && ( +
+
+ {t("nav.user.other-organizations")} +
+
+ {orgs + ?.filter((org: { _id: string }) => org._id !== currentOrg?._id) + .map((org: { _id: string; name: string }) => ( +
null} + role="button" + tabIndex={0} + key={guidGenerator()} + onClick={() => { + localStorage.setItem("orgData.id", org._id); + router.reload(); + }} + className="flex w-full cursor-pointer flex-row items-center justify-start rounded-md p-1.5 hover:bg-white/5" + > +
+ {org.name.charAt(0)} +
+
+

{org.name}

+
+
+ ))} +
+
+ )} +
+ + {({ active }) => ( + + )} + +
+
+
+
+
+
+ {subscription && subscription.status === "trialing" && subscription.trial_end && ( +
+

+ {`Currently trialing the ${formatPlanSlug(subscription.slug)} plan until ${formatDate(subscription.trial_end).formattedDate} - ${formatDate(subscription.trial_end).remainingDays} day(s) left. `} + Add a card to avoid being downgraded to the Starter plan afterward → +

+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/views/DashboardPage/DashboardPage.tsx b/frontend/src/views/DashboardPage/DashboardPage.tsx index f77e0211a..1a3b0f73e 100644 --- a/frontend/src/views/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/DashboardPage/DashboardPage.tsx @@ -340,7 +340,10 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => { } }; - const onAppendSecret = () => append(DEFAULT_SECRET_VALUE); + const onAppendSecret = () => { + setSearchFilter(""); + append(DEFAULT_SECRET_VALUE) + }; const onSaveSecret = async ({ secrets: userSec = [], isSnapshotMode }: FormData) => { if (isSnapshotMode) { @@ -700,6 +703,7 @@ export const DashboardPage = ({ envFromTop }: { envFromTop: string }) => { }); } prepend(DEFAULT_SECRET_VALUE, { shouldFocus: false }); + setSearchFilter(""); }} isDisabled={isReadOnly || isRollbackMode} variant="outline_bg" diff --git a/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx b/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx index 201012fa1..6c86b3fce 100644 --- a/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx +++ b/frontend/src/views/DashboardPage/components/EnvComparisonRow/EnvComparisonRow.tsx @@ -4,8 +4,7 @@ import { faEye, faEyeSlash, faKey, faMinus } from "@fortawesome/free-solid-svg-i import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useSyntaxHighlight } from "@app/hooks"; - -import { useToggle } from "~/hooks/useToggle"; +import { useToggle } from "@app/hooks/useToggle"; type Props = { secrets: any[] | undefined; @@ -71,6 +70,8 @@ const DashboardInput = ({ ref={ref} className={`absolute top-1.5 left-3.5 z-10 overflow-auto font-mono text-sm transition-all no-scrollbar ${ isOverridden && "text-primary-300" + } ${ + (value || "") === "" && "text-mineshaft-400" }`} style={{ height: `${maxMultilineHeight}px`, width: "calc(100% - 12px)" }} > diff --git a/frontend/src/views/DashboardPage/components/SecretInputRow/MaskedInput.tsx b/frontend/src/views/DashboardPage/components/SecretInputRow/MaskedInput.tsx index a41589a1f..d4663556d 100644 --- a/frontend/src/views/DashboardPage/components/SecretInputRow/MaskedInput.tsx +++ b/frontend/src/views/DashboardPage/components/SecretInputRow/MaskedInput.tsx @@ -94,6 +94,8 @@ export const MaskedInput = ({ isReadOnly, isSecretValueHidden, index, isOverridd ref={ref} className={`absolute top-1.5 left-3.5 z-10 w-full overflow-auto font-mono text-sm transition-all no-scrollbar ${ isOverridden && "text-primary-300" + } ${ + (value || "") === "" && "text-mineshaft-400" }`} style={{ height: `${maxMultilineHeight}px`, width: "calc(100% - 12px)" }} > diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx index e1a39ca06..23a4be24d 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx @@ -45,7 +45,7 @@ export const PreviewSection = () => { return (
{!isSubscriptionLoading && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && ( -
+

Become Infisical

Unlimited members, projects, RBAC, smart alerts, and so much more