diff --git a/app/src/components/MigrateTempFlowchartsModal.tsx b/app/src/components/MigrateTempFlowchartsModal.tsx deleted file mode 100644 index dda1c52fb..000000000 --- a/app/src/components/MigrateTempFlowchartsModal.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as Dialog from "@radix-ui/react-dialog"; -import { Close, Content, Overlay } from "../ui/Dialog"; -import { Trans } from "@lingui/macro"; -import { Button2 } from "../ui/Shared"; -import { getTemporaryCharts } from "../lib/getTemporaryCharts"; -import { ReactNode, useState } from "react"; -import * as Progress from "@radix-ui/react-progress"; -import { makeChart } from "../lib/queries"; -import { titleToLocalStorageKey } from "../lib/helpers"; -import { useSession } from "./AppContextProvider"; -import { getDefaultChart } from "../lib/getDefaultChart"; - -/** - * A modal to handle migrating temporary flowcharts into hosted ones - */ -export function MigrateTempFlowchartsModal({ - children, -}: { - children: ReactNode; -}) { - const [numCharts] = useState(() => getTemporaryCharts().length); - const [isMigrating, setIsMigrating] = useState(false); - const [migrated, setMigrated] = useState(0); - const userId = useSession()?.user.id; - if (!userId) return null; - return ( - - {children} - - - - - Migrate Flowcharts - - -

- Click the button below to migrate all of your temporary flowcharts - into hosted ones. -

-

- Do not close this window. The page will refresh when the migration - is complete. -

-
- { - setIsMigrating(true); - migrateTemporaryFlowcharts(setMigrated, userId); - }} - > - Start Migration - -

- {migrated} / {numCharts} -

- - - - {isMigrating ? null : } -
-
-
- ); -} - -async function migrateTemporaryFlowcharts( - setMigrated: (n: number) => void, - userId: string -) { - const temporaryCharts = getTemporaryCharts(); - - let i = 0; - for (const key of temporaryCharts) { - const name = key ? key : "flowchart.fun"; - const localStorageKey = titleToLocalStorageKey(key); - const chart = localStorage.getItem(localStorageKey); - - if (chart) { - const response = await makeChart({ - name, - chart, - user_id: userId, - }); - - if (!response || response.error) { - // don't delete the temporary chart - continue; - } - - // delete the temporary chart, unless it's the default one, in which case, reset it - if (key) { - localStorage.removeItem(localStorageKey); - } else { - localStorage.setItem(localStorageKey, getDefaultChart()); - } - } - - setMigrated(++i); - } - - // refresh the page - window.location.reload(); -} diff --git a/app/src/components/PaymentStepper.tsx b/app/src/components/PaymentStepper.tsx deleted file mode 100644 index 02b10b96c..000000000 --- a/app/src/components/PaymentStepper.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { t, Trans } from "@lingui/macro"; -import { useStripe } from "@stripe/react-stripe-js"; -import { - StripeElements, - StripeElementsOptionsClientSecret, -} from "@stripe/stripe-js"; -import { ArrowRight, RocketLaunch, WarningCircle } from "phosphor-react"; -import { useEffect, useRef, useState } from "react"; -import { useQuery } from "react-query"; - -import { useLightOrDarkMode, useSession } from "../lib/hooks"; -import { Button2 } from "../ui/Shared"; -import { Warning } from "./Warning"; -import { sendLoopsEvent } from "../lib/sendLoopsEvent"; -import { PlanButton } from "./PlanButton"; -import { PaymentStepperTitle } from "./PaymentStepperTitle"; - -/** - * This component allows the user to select a plan, - * enter their email if they are not logged in, - * and enter their payment information. - */ -export function PaymentStepper() { - const session = useSession(); - const sessionEmail = session?.user?.email; - const [plan, setPlan] = useState(() => { - return window.location.hash === "#annually" ? "yearly" : null; - }); - const [confirmPlan, setConfirmPlan] = useState(false); - - const [email, setEmail] = useState(""); - const [confirmEmail, setConfirmEmail] = useState(false); - - const subscriptionDetails = useQuery( - ["subscriptionDetails", email, plan], - () => plan && getSubscriptionDetails(email, plan), - { - enabled: Boolean(confirmEmail && plan && email), - retry: false, - onError: () => { - setConfirmEmail(false); - }, - } - ); - const [parent] = useAutoAnimate(); - - /** - * watch the session email and if the user is logged in auto-fill this step! - */ - useEffect(() => { - if (sessionEmail) { - setEmail(sessionEmail); - setConfirmEmail(true); - } - }, [sessionEmail]); - - let step = "one"; - if (confirmPlan) step = "two"; - if ( - confirmPlan && - subscriptionDetails.data?.subscriptionId && - subscriptionDetails.data?.clientSecret - ) - step = "three"; - - return ( -
-
- {step === "one" && ( -
- - Choose a Plan - -
- { - setPlan("monthly"); - }} - className="mr-2 aria-[current=true]:text-blue-500" - title={t`Monthly`} - price={t`$3 per month`} - data-testid="monthly-plan-button" - /> - { - setPlan("yearly"); - }} - className="aria-[current=true]:text-blue-500" - title={t`Yearly`} - price={t`$30 per year`} - data-testid="yearly-plan-button" - extra={ - - Save 16% - - } - /> -
- setConfirmPlan(true)} - disabled={plan === null} - className="mt-8 disabled:!opacity-100 !py-4 font-bold" - rightIcon={} - color="blue" - > - Continue - -
- )} - {step === "two" && ( -
{ - e.preventDefault(); - const email = new FormData(e.target as HTMLFormElement).get( - "email" - ) as string; - setEmail(email); - setConfirmEmail(true); - }} - autoComplete="false" - className="grid gap-4" - > - - Enter your email - -
- - {t`Make sure you use the same email you will use to log in.`} - - -
- } - color="blue" - isLoading={subscriptionDetails.isLoading} - > - Continue - - {subscriptionDetails.error ? ( -
- - {(subscriptionDetails.error as Error).message} - -
- ) : null} -
- )} - {step === "three" && ( -
- {t`Activate your Account`} - -
- )} -
-
- ); -} - -function PaymentForm({ - clientSecret, - email, -}: { - clientSecret: string; - email: string; -}) { - const stripe = useStripe(); - const elements = useRef(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const mode = useLightOrDarkMode(); - const session = useSession(); - - useEffect(() => { - if (!stripe) return; - const options: StripeElementsOptionsClientSecret = { - clientSecret, - appearance: { - theme: mode === "dark" ? "night" : undefined, - variables: { - colorPrimary: "#5c6fff", - colorPrimaryText: "#ffffff", - }, - }, - }; - - elements.current = stripe.elements(options); - const paymentElement = elements.current.create("payment"); - paymentElement?.mount("#payment-element"); - }, [clientSecret, mode, stripe]); - - async function signup() { - if (!stripe || !elements.current) return; - - setLoading(true); - - const returnUrl = session - ? `${window.location.origin}/` - : `${window.location.origin}/l#success`; - - // send loops event - sendLoopsEvent({ - email, - eventName: "new_subscriber", - }); - - const { error } = await stripe.confirmPayment({ - //`Elements` instance that was used to create the Payment Element - elements: elements.current, - confirmParams: { - return_url: returnUrl, - }, - }); - - if (error) { - // This point will only be reached if there is an immediate error when - // confirming the payment. Show error to your customer (for example, payment - // details incomplete) - setError(error.message ?? `An unknown error occurred`); - } else { - // Your customer will be redirected to your `return_url`. For some payment - // methods like iDEAL, your customer will be redirected to an intermediate - // site first to authorize the payment, then redirected to the `return_url`. - } - } - - return ( -
{ - e.preventDefault(); - signup().finally(() => setLoading(false)); - }} - > -
- } - isLoading={loading} - > - Sign Up - - {error && ( - -
- - {error} -
-
- )} - - ); -} - -/** - * Creates incomplete subscription and returns client secret needed to complete it - */ -async function getSubscriptionDetails( - email: string, - plan: "monthly" | "yearly" -): Promise<{ - subscriptionId: string; - clientSecret: string; -}> { - // hit some api endpoint to get the payment details - const response = await fetch("/api/get-signup-client-secret", { - method: "POST", - body: JSON.stringify({ email, plan }), - headers: { - "Content-Type": "application/json", - }, - }).then((res) => res.json()); - - // if there is an error, throw it - if (response.error) throw new Error(response.error.message); - - // return the payment token - return response; -} - -function Description({ - children, - className, -}: { - children: React.ReactNode; - className?: string; -}) { - return ( -

- {children} -

- ); -} diff --git a/app/src/components/WelcomeMessage.tsx b/app/src/components/WelcomeMessage.tsx deleted file mode 100644 index 57b5968d5..000000000 --- a/app/src/components/WelcomeMessage.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { t } from "@lingui/macro"; - -import { ReactComponent as Success } from "./Success.svg"; - -/** - * The message that is displayed when user - * is redirected to the login page - * after first signing up - */ -export function WelcomeMessage() { - return ( - <> -
- -

{t`Welcome to Flowchart Fun Pro!`}

-

- {t`Log in to start creating flowcharts.`} -

-
- - ); -} diff --git a/app/src/pages/Pricing.tsx b/app/src/pages/Pricing.tsx index 3dfc6f032..c97a5be61 100644 --- a/app/src/pages/Pricing.tsx +++ b/app/src/pages/Pricing.tsx @@ -7,7 +7,6 @@ import styles from "./Pricing.module.css"; import { PostHogProvider } from "posthog-js/react"; import posthog from "posthog-js"; import classNames from "classnames"; -import { PaymentStepper } from "../components/PaymentStepper"; import { Checkout } from "../components/Checkout"; const posthogToken = process.env.REACT_APP_PUBLIC_POSTHOG_KEY;