Merge pull request #730 from Infisical/main

Catching up the branch
This commit is contained in:
vmatsiiako
2023-07-11 16:19:39 -07:00
committed by GitHub
27 changed files with 307 additions and 118 deletions
@@ -1,4 +1,4 @@
import { useState } from "react";
import { FormEvent, useState } from "react";
import { useTranslation } from "react-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -32,7 +32,8 @@ export default function InitialLoginStep({
const [isLoading, setIsLoading] = useState(false);
const [loginError, setLoginError] = useState(false);
const handleLogin = async () => {
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
try {
if (!email || !password) {
return;
@@ -101,7 +102,7 @@ export default function InitialLoginStep({
setIsLoading(false);
}
return <div className='flex flex-col mx-auto w-full justify-center items-center'>
return <form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
{/* <div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
<Button
@@ -146,7 +147,7 @@ export default function InitialLoginStep({
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
<Button
onClick={async () => handleLogin()}
type="submit"
size="sm"
isFullWidth
className='h-12'
@@ -183,5 +184,5 @@ export default function InitialLoginStep({
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>Recover your account</span>
</Link>
</div>
</div>
</form>
}
@@ -114,6 +114,7 @@ export default function CodeInputStep({
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<Button
type="submit"
onClick={incrementStep}
size="sm"
isFullWidth
@@ -70,6 +70,7 @@ export default function EnterEmailStep({
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<Button
type="submit"
onClick={emailCheck}
size="sm"
isFullWidth
@@ -248,7 +248,6 @@ export default function UserInfoStep({
placeholder=""
onChange={(e) => setAttributionSource(e.target.value)}
value={attributionSource}
isRequired
className="h-12"
/>
</div>
@@ -301,6 +300,7 @@ export default function UserInfoStep({
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full">
<Button
type="submit"
onClick={signupErrorCheck}
size="sm"
isFullWidth
@@ -1,9 +1,10 @@
import Link from "next/link";
import { useSubscription } from "@app/context";
import { useOrganization, useSubscription } from "@app/context";
import {
useGetOrgTrialUrl
} from "@app/hooks/api";
import { Button } from "../Button";
import { Modal, ModalClose, ModalContent } from "../Modal";
import { Modal, ModalContent } from "../Modal";
type Props = {
isOpen?: boolean;
@@ -13,32 +14,61 @@ type Props = {
export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => {
const { subscription } = useSubscription();
const { currentOrg } = useOrganization();
const { mutateAsync, isLoading } = useGetOrgTrialUrl();
const link = (subscription && subscription.slug !== null)
? `/settings/billing/${localStorage.getItem("projectData.id") as string}`
: "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 (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Unleash Infisical's Full Power"
footerContent={[
<Link
href={link}
key="upgrade-plan"
>
<Button className="mr-4 ml-2 mb-2">Upgrade Plan</Button>
</Link>,
<ModalClose asChild key="upgrade-plan-cancel">
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
]}
>
<p className="mb-2 text-bunker-300">{text}</p>
<p className="text-bunker-300">
Upgrade and get access to this, as well as to other powerful enhancements.
</p>
<div className="mt-8 flex items-center">
<Button
isLoading={isLoading}
colorSchema="primary"
onClick={handleUpgradeBtnClick}
className="mr-4"
>
{(subscription && !subscription.has_used_trial) ? "Start Pro Free Trial" : "Upgrade Plan"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => onOpenChange && onOpenChange(false)}
>
Cancel
</Button>
</div>
</ModalContent>
</Modal>
)
+3 -1
View File
@@ -12,5 +12,7 @@ export {
useGetOrgPlanTable,
useGetOrgPmtMethods,
useGetOrgTaxIds,
useGetOrgTrialUrl,
useRenameOrg,
useUpdateOrgBillingDetails} from "./queries";
useUpdateOrgBillingDetails
} from "./queries";
@@ -10,8 +10,9 @@ import {
PlanBillingInfo,
PmtMethod,
ProductsTable,
RenameOrgDTO,
TaxID} from "./types";
RenameOrgDTO,
TaxID
} from "./types";
const organizationKeys = {
getUserOrganization: ["organization"] as const,
@@ -47,6 +48,24 @@ export const useRenameOrg = () => {
});
};
export const useGetOrgTrialUrl = () => {
return useMutation({
mutationFn: async ({
orgId,
success_url
}: {
orgId: string;
success_url: string;
}) => {
const { data: { url } } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
success_url
})
return url;
}
});
};
export const useGetOrgPlanBillingInfo = (organizationId: string) => {
return useQuery({
queryKey: organizationKeys.getOrgPlanBillingInfo(organizationId),
@@ -15,4 +15,5 @@ export type SubscriptionPlan = {
environmentLimit: number;
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
trial_end: number | null;
has_used_trial: boolean;
};
@@ -3,7 +3,6 @@
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";
@@ -22,7 +21,9 @@ import {TFunction} from "i18next";
import guidGenerator from "@app/components/utilities/randomId";
import { useOrganization, useSubscription,useUser } from "@app/context";
import { useLogoutUser } from "@app/hooks/api";
import {
useGetOrgTrialUrl,
useLogoutUser} from "@app/hooks/api";
const supportOptions = (t: TFunction) => [
[
@@ -67,6 +68,7 @@ export const Navbar = () => {
const { subscription } = useSubscription();
const { currentOrg, orgs } = useOrganization();
const { mutateAsync } = useGetOrgTrialUrl();
const { user } = useUser();
const logout = useLogoutUser();
@@ -96,37 +98,6 @@ export const Navbar = () => {
}
};
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 (
<div className="z-[70] border-b border-mineshaft-500 bg-mineshaft-900 text-white">
<div className="flex w-full justify-between px-4">
@@ -346,12 +317,25 @@ export const Navbar = () => {
</Menu>
</div>
</div>
{subscription && subscription.status === "trialing" && subscription.trial_end && (
<div className="w-full mx-auto border-t border-mineshaft-500">
<p className="text-center py-4 text-sm">
{`Currently trialing the ${formatPlanSlug(subscription.slug)} plan until ${formatDate(subscription.trial_end).formattedDate} - ${formatDate(subscription.trial_end).remainingDays} day(s) left. `}
<Link href={`/settings/billing/${localStorage.getItem("projectData.id")}`}>Add a card to avoid being downgraded to the Starter plan afterward &rarr;</Link>
</p>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
<div className="w-full mx-auto border-t border-mineshaft-500 text-center">
<button
type="button"
onClick={async () => {
if (!subscription || !currentOrg) return;
// direct user to start pro trial
const url = await mutateAsync({
orgId: currentOrg._id,
success_url: window.location.href
});
window.location.href = url;
}}
className="text-center py-4 text-sm mx-auto"
>
You are currently on the <span className="font-semibold">Starter</span> plan. Unlock the full power of Infisical on the <span className="font-semibold">Pro Free Trial &rarr;</span>
</button>
</div>
)}
</div>
+16 -9
View File
@@ -1,6 +1,6 @@
import crypto from "crypto";
import { useState } from "react";
import { FormEvent, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
@@ -24,6 +24,7 @@ const client = new jsrp.client();
export default function PasswordReset() {
const [verificationToken, setVerificationToken] = useState("");
const [step, setStep] = useState(1);
const [loading, setLoading] = useState(false);
const [backupKey, setBackupKey] = useState("");
const [privateKey, setPrivateKey] = useState("");
const [newPassword, setNewPassword] = useState("");
@@ -38,7 +39,8 @@ export default function PasswordReset() {
const email = (parsedUrl.to as string)?.replace(" ", "+").trim();
// Unencrypt the private key with a backup key
const getEncryptedKeyHandler = async () => {
const getEncryptedKeyHandler = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const result = await getBackupEncryptedPrivateKey({ verificationToken });
@@ -57,7 +59,8 @@ export default function PasswordReset() {
};
// If everything is correct, reset the password
const resetPasswordHandler = async () => {
const resetPasswordHandler = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const errorCheck = passwordCheck({
password: newPassword,
setPasswordErrorLength,
@@ -125,6 +128,7 @@ export default function PasswordReset() {
if (response?.status === 200) {
router.push("/login");
}
setLoading(false)
});
}
);
@@ -162,7 +166,7 @@ export default function PasswordReset() {
// Input backup key
const stepInputBackupKey = (
<div className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
<form onSubmit={getEncryptedKeyHandler} className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
<p className="mx-auto mb-4 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
Enter your backup key
</p>
@@ -186,18 +190,19 @@ export default function PasswordReset() {
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
<Button
type="submit"
text="Submit Backup Key"
onButtonPressed={() => getEncryptedKeyHandler()}
onButtonPressed={() => {}}
size="lg"
/>
</div>
</div>
</div>
</form>
);
// Enter new password
const stepEnterNewPassword = (
<div className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
<form onSubmit={resetPasswordHandler} className="my-32 mx-1 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pt-6 pb-3 drop-shadow-xl md:max-w-lg md:px-6">
<p className="mx-auto flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
Enter new password
</p>
@@ -269,13 +274,15 @@ export default function PasswordReset() {
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
<Button
type="submit"
text="Submit New Password"
onButtonPressed={() => resetPasswordHandler()}
onButtonPressed={() => setLoading(true)}
size="lg"
loading={loading}
/>
</div>
</div>
</div>
</form>
);
return (
+17 -14
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { FormEvent, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
@@ -12,6 +12,7 @@ import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import SendEmailOnPasswordReset from "./api/auth/SendEmailOnPasswordReset";
export default function VerifyEmail() {
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const [step, setStep] = useState(1);
const { data: serverDetails } = useFetchServerStatus();
@@ -27,6 +28,18 @@ export default function VerifyEmail() {
}
};
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
if (serverDetails?.emailConfigured) {
sendVerificationEmail();
} else {
handlePopUpOpen("setUpEmail");
setLoading(false);
}
};
return (
<div className="flex h-screen flex-col justify-start bg-bunker-800 px-6">
<Head>
@@ -45,7 +58,7 @@ export default function VerifyEmail() {
</div>
</Link>
{step === 1 && (
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker py-4 px-6 pt-8 drop-shadow-xl">
<form onSubmit={onSubmit} className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker px-6 py-4 pt-8 drop-shadow-xl">
<p className="mx-auto mb-6 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
Forgot your password?
</p>
@@ -67,20 +80,10 @@ export default function VerifyEmail() {
</div>
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
<Button
text="Continue"
onButtonPressed={() => {
if (serverDetails?.emailConfigured) {
sendVerificationEmail();
} else {
handlePopUpOpen("setUpEmail");
}
}}
size="lg"
/>
<Button type="submit" text="Continue" size="lg" onButtonPressed={() => {}} loading={loading} />
</div>
</div>
</div>
</form>
)}
{step === 2 && (
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker py-4 px-6 pt-8 drop-shadow-xl">
@@ -2,15 +2,18 @@ import { Button } from "@app/components/v2";
import { useOrganization,useSubscription } from "@app/context";
import {
useCreateCustomerPortalSession,
useGetOrgPlanBillingInfo} from "@app/hooks/api";
useGetOrgPlanBillingInfo,
useGetOrgTrialUrl
} from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { ManagePlansModal } from "./ManagePlansModal";
export const PreviewSection = () => {
const { currentOrg } = useOrganization();
const { subscription, isLoading: isSubscriptionLoading } = useSubscription();
const { subscription } = useSubscription();
const { data, isLoading } = useGetOrgPlanBillingInfo(currentOrg?._id ?? "");
const getOrgTrialUrl = useGetOrgTrialUrl();
const createCustomerPortalSession = useCreateCustomerPortalSession();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
@@ -42,19 +45,41 @@ export const PreviewSection = () => {
.replace(/-/g, " ");
}
const handleUpgradeBtnClick = async () => {
try {
if (!subscription || !currentOrg) return;
if (!subscription.has_used_trial) {
// direct user to start pro trial
const url = await getOrgTrialUrl.mutateAsync({
orgId: currentOrg._id,
success_url: window.location.href
});
window.location.href = url;
} else {
// open compare plans modal
handlePopUpOpen("managePlan");
}
} catch (err) {
console.error(err);
}
}
return (
<div>
{!isSubscriptionLoading && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && (
{subscription && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && (
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600 max-w-screen-lg">
<div className="flex-1">
<h2 className="text-xl font-semibold text-mineshaft-50">Become Infisical</h2>
<p className="text-gray-400 mt-4">Unlimited members, projects, RBAC, smart alerts, and so much more</p>
</div>
<Button
onClick={() => handlePopUpOpen("managePlan")}
// onClick={() => handlePopUpOpen("managePlan")}
onClick={() => handleUpgradeBtnClick()}
color="mineshaft"
>
Upgrade
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
</Button>
</div>
)}