diff --git a/app/lib/models/lead.server.ts b/app/lib/models/lead.server.ts
index d2314c76..26c3bda5 100644
--- a/app/lib/models/lead.server.ts
+++ b/app/lib/models/lead.server.ts
@@ -30,3 +30,32 @@ export async function registerChallengeLead(
};
}
}
+
+export async function registerLead(request: Request, email: string) {
+ return axios
+ .post(
+ `${environment().API_HOST}/leads`,
+ {
+ email,
+ tags: ["lead-assine"],
+ },
+ {},
+ )
+ .then((res) => {
+ return { success: "Cadastro realizado com sucesse!" };
+ })
+ .catch((error) => {
+ let errorMsg =
+ "Não foi possível realizar o cadastro. Por favor, tente novamente.";
+
+ if (error.response.status === 409) {
+ errorMsg = "Esse email já está cadastrado em nossa lista.";
+ } else if (error.response.status === 422) {
+ errorMsg = "Por favor, insira um email válido.";
+ }
+
+ return {
+ error: errorMsg,
+ };
+ });
+}
diff --git a/app/routes/_api/leads/index.tsx b/app/routes/_api/leads/index.tsx
new file mode 100644
index 00000000..f29b3c6c
--- /dev/null
+++ b/app/routes/_api/leads/index.tsx
@@ -0,0 +1,42 @@
+import { abort404 } from "~/lib/utils/responses.server";
+import { isRouteErrorResponse, useRouteError } from "@remix-run/react";
+import NotFound from "~/components/features/error-handling/not-found";
+import { Error500 } from "~/components/features/error-handling/500";
+import { registerChallengeLead, registerLead } from "~/lib/models/lead.server";
+
+export async function action({ request }: { request: Request }) {
+ const formData = await request.formData();
+ const intent = formData.get("intent") as string;
+
+ const email = formData.get("email") as string;
+ // const tag = formData.get("tag") as string | undefined;
+
+ switch (intent) {
+ case "register-lead-assine":
+ return registerLead(request, email);
+ case "register-lead-challenge":
+ return registerChallengeLead(request, email);
+ }
+}
+
+export async function loader() {
+ return abort404();
+}
+
+export function ErrorBoundary() {
+ const error = useRouteError();
+
+ if (isRouteErrorResponse(error)) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+}
+
+export default function Leads() {
+ return null;
+}
diff --git a/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/_layout.tsx b/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/_layout.tsx
index 9cd34aed..97dc1912 100644
--- a/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/_layout.tsx
+++ b/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/_layout.tsx
@@ -47,8 +47,7 @@ import {
SelectValue,
} from "~/components/ui/select";
-import MobileSignupForm from "~/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/mobile-signup-form";
-import { registerChallengeLead } from "~/lib/models/lead.server";
+import MobileSignupForm from "./components/mobile-signup-form";
export const meta = ({ data, params }: any) => {
// para não quebrar se não houver challenge ainda.
@@ -123,10 +122,6 @@ export async function action({ request }: ActionFunctionArgs) {
case "requestCertificate":
const certifiableId = formData.get("certifiable_id") as string;
return requestCertificate(request, "ChallengeUser", certifiableId);
- case "register-lead":
- const email = formData.get("email") as string;
-
- return registerChallengeLead(request, email);
default:
return null;
}
diff --git a/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/mobile-signup-form.tsx b/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/mobile-signup-form.tsx
index 2445d19b..080c3545 100644
--- a/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/mobile-signup-form.tsx
+++ b/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/mobile-signup-form.tsx
@@ -1,15 +1,11 @@
-import { useLocation } from "@remix-run/react";
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
-import ResponsiveEmailSignup from "~/components/features/auth/responsive-email-signup";
+import ResponsiveEmailSignup from "./responsive-email-signup";
import { useMediaQuery } from "~/lib/hooks/use-media-query";
import type { User } from "~/lib/models/user.server";
function MobileSignupForm({ user }: { user: User | null }) {
const isMobile = useMediaQuery("(max-width: 768px)");
- const location = useLocation();
-
- const slug = location.pathname.split("mini-projetos/")[1];
const [hideOnMobile, setHideOnMobile] = useState(false);
useEffect(() => {
@@ -29,7 +25,7 @@ function MobileSignupForm({ user }: { user: User | null }) {
}}
className="w-full bottom-0 h-20 z-20 bg-gradient-to-tr animate-bg from-background-50 to-background-100 border-background-100 dark:from-background-700 dark:to-background-900 fixed shadow-lg border-t dark:border-background-700 flex items-center justify-center"
>
-
+
)
);
diff --git a/app/components/features/auth/responsive-email-signup/index.tsx b/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/responsive-email-signup.tsx
similarity index 58%
rename from app/components/features/auth/responsive-email-signup/index.tsx
rename to app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/responsive-email-signup.tsx
index 5979f06e..3c921b76 100644
--- a/app/components/features/auth/responsive-email-signup/index.tsx
+++ b/app/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/responsive-email-signup.tsx
@@ -1,27 +1,38 @@
-import {
- Form,
- useActionData,
- useNavigation,
- useSubmit,
-} from "@remix-run/react";
+import { useActionData, useFetcher } from "@remix-run/react";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { ResponsiveDialog } from "~/components/ui/responsive-dialog";
import LoadingButton from "~/components/features/form/loading-button";
+import { useEffect } from "react";
+import { useToasterWithSound } from "~/lib/hooks/useToasterWithSound";
-export default function ResponsiveEmailSignup({ slug }: { slug: string }) {
- const submit = useSubmit();
+interface FetcherData {
+ error?: string;
+ success?: string;
+}
+
+export default function ResponsiveEmailSignup() {
const errors = useActionData();
- const transition = useNavigation();
+ const fetcher = useFetcher();
+ const { showSuccessToast, showErrorToast } = useToasterWithSound();
- const status = transition.state;
+ const status = fetcher.state;
let isSuccessfulSubmission = status === "idle" && errors === null;
+ const fetcherData = fetcher.data as FetcherData;
+ const errorMsg = fetcherData && fetcherData.error ? fetcherData.error : null;
+ const successMsg =
+ fetcherData && fetcherData.success ? fetcherData.success : null;
+
+ useEffect(() => {
+ if (successMsg) showSuccessToast(successMsg);
+ if (errorMsg) showErrorToast(errorMsg);
+ }, [successMsg, errorMsg, showErrorToast, showSuccessToast]);
+
async function handleSubmit(event: React.FormEvent) {
- submit(event.currentTarget, {
+ fetcher.submit(event.currentTarget.form, {
method: "post",
- action: `/mini-projetos/${slug}`,
});
}
@@ -33,7 +44,12 @@ export default function ResponsiveEmailSignup({ slug }: { slug: string }) {
triggerButtonSize="lg"
>
<>
-
+
>
);
diff --git a/app/routes/_layout-raw/assine.index/components/responsive-dialog-assine.tsx b/app/routes/_layout-raw/assine.index/components/responsive-dialog-assine.tsx
new file mode 100644
index 00000000..57c7eb43
--- /dev/null
+++ b/app/routes/_layout-raw/assine.index/components/responsive-dialog-assine.tsx
@@ -0,0 +1,126 @@
+import * as React from "react";
+import { Button } from "~/components/ui/button";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "~/components/ui/dialog";
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from "~/components/ui/drawer";
+import { useMediaQuery } from "~/lib/hooks/use-media-query";
+
+interface ResponsiveDialogProps {
+ children: React.ReactNode;
+ triggerLabel?: string;
+ title: string;
+ description: string;
+ drawerCancelLabel?: string;
+ open?: boolean;
+ triggerClassName?: string;
+ onOpenChange?: (open: boolean) => void;
+}
+
+export function ResponsiveDialog({
+ children,
+ title,
+ description,
+ drawerCancelLabel = "Cancelar",
+ open,
+ onOpenChange,
+}: ResponsiveDialogProps) {
+ const isDesktop = useMediaQuery("(min-width: 768px)");
+
+ const scroll = () => {
+ const section = document.querySelector("#price-card");
+ section?.scrollIntoView({ behavior: "smooth", block: "start" });
+ };
+ if (isDesktop) {
+ return (
+
+ );
+ }
+
+ return (
+ {
+ if (!isOpen) {
+ scroll();
+ }
+ if (onOpenChange) {
+ onOpenChange(isOpen);
+ }
+ }}
+ >
+
+
+
+
+
+ {title}
+ {description}
+
+ {children}
+
+
+
+ {" "}
+
+
+
+ );
+}
diff --git a/app/routes/_layout-raw/assine.index/components/responsive-email-signup.tsx b/app/routes/_layout-raw/assine.index/components/responsive-email-signup.tsx
new file mode 100644
index 00000000..87013ca9
--- /dev/null
+++ b/app/routes/_layout-raw/assine.index/components/responsive-email-signup.tsx
@@ -0,0 +1,89 @@
+import { useActionData, useFetcher } from "@remix-run/react";
+
+import { Input } from "~/components/ui/input";
+import { Label } from "~/components/ui/label";
+import { ResponsiveDialog } from "./responsive-dialog-assine";
+import LoadingButton from "~/components/features/form/loading-button";
+import { useEffect } from "react";
+import { useToasterWithSound } from "~/lib/hooks/useToasterWithSound";
+
+interface FetcherData {
+ error?: string;
+ success?: string;
+}
+
+export default function ResponsiveEmailSignup() {
+ const errors = useActionData();
+ const fetcher = useFetcher();
+ const { showSuccessToast, showErrorToast } = useToasterWithSound();
+
+ const status = fetcher.state;
+ let isSuccessfulSubmission = status === "idle" && errors === null;
+
+ const fetcherData = fetcher.data as FetcherData;
+ const errorMsg = fetcherData && fetcherData.error ? fetcherData.error : null;
+ const successMsg =
+ fetcherData && fetcherData.success ? fetcherData.success : null;
+
+ // const scrollFunction = () => {
+ // const section = document.querySelector("#price-card");
+ // section?.scrollIntoView({ behavior: "smooth", block: "start" });
+ // };
+
+ useEffect(() => {
+ if (successMsg) showSuccessToast(successMsg);
+ if (errorMsg) showErrorToast(errorMsg);
+ }, [successMsg, errorMsg, showErrorToast, showSuccessToast]);
+
+ async function handleSubmit(event: React.FormEvent) {
+ fetcher.submit(event.currentTarget.form, {
+ method: "post",
+ });
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Ao cadastrar você receberá informações sobre o Codante pelo email.
+ Não se preocupe, não vamos enviar spam.
+
+
+
+
+ Cadastrar
+
+
+
+
+ {/* */}
+
+ );
+}
diff --git a/app/routes/_layout-raw/assine.index/index.tsx b/app/routes/_layout-raw/assine.index/index.tsx
index 062a1449..beba0e91 100644
--- a/app/routes/_layout-raw/assine.index/index.tsx
+++ b/app/routes/_layout-raw/assine.index/index.tsx
@@ -2,6 +2,7 @@ import { json, redirect } from "@remix-run/node";
import {
Link,
isRouteErrorResponse,
+ useFetcher,
useLoaderData,
useRouteError,
} from "@remix-run/react";
@@ -19,7 +20,7 @@ import {
} from "~/components/ui/cards/pricing/pricing-data";
import { useColorMode } from "~/lib/contexts/color-mode-context";
import UserAvatar from "~/components/ui/user-avatar";
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
import SubmissionCard from "~/routes/_layout-app/_mini-projetos/mini-projetos_.$slug_/components/submission-card";
import { ProgressivePracticeContent } from "./components/progressive-practice";
@@ -38,6 +39,11 @@ import axios from "axios";
import type { AxiosError } from "axios";
import type { Subscription } from "~/lib/models/subscription.server";
import { environment } from "~/lib/models/environment";
+import { Card, CardContent, CardTitle } from "~/components/ui/cards/card";
+import { Input } from "~/components/ui/input";
+import { Button } from "~/components/ui/button";
+import toast from "react-hot-toast";
+import ResponsiveEmailSignup from "./components/responsive-email-signup";
export const loader = async () => {
return json({
@@ -126,25 +132,75 @@ export function ErrorBoundary() {
}
function CodanteProButton() {
- const scroll = () => {
- const section = document.querySelector("#pricing");
- section?.scrollIntoView({ behavior: "smooth", block: "start" });
- };
+ // const scroll = () => {
+ // const section = document.querySelector("#price-card");
+ // section?.scrollIntoView({ behavior: "smooth", block: "start" });
+ // };
+
+ return ;
+}
+
+interface FetcherData {
+ error?: string;
+ success?: string;
+}
+
+function LeadForm() {
+ const fetcher = useFetcher();
+ const emailRef = useRef(null);
+
+ const isSubmittingOrLoading =
+ fetcher.state === "submitting" || fetcher.state === "loading";
+
+ function handleLeadClickButton() {
+ if (emailRef?.current?.value) {
+ fetcher.submit(
+ { intent: "register-lead", email: emailRef.current.value },
+ {
+ method: "post",
+ action: "/leads?index",
+ },
+ );
+ }
+ }
+
+ const fetcherData = fetcher.data as FetcherData;
+ const errorMsg = fetcherData && fetcherData.error ? fetcherData.error : null;
+ const successMsg =
+ fetcherData && fetcherData.success ? fetcherData.success : null;
+ // if (successMsg) toast.success("E-mail cadastrado com sucesso!");
+
+ useEffect(() => {
+ if (successMsg) toast.success("E-mail cadastrado com sucesso!");
+ }, [successMsg]);
return (
-
+
+ Quero receber novidades!
+
+
+ Cadastre seu e-mail e fique por dentro das nossas últimas
+ atualizações.{" "}
+
+
+
+
+
+
);
}
@@ -390,6 +446,7 @@ function Headline() {
))}
+
>
);
@@ -1819,6 +1876,7 @@ function Pricing() {
hidden: { opacity: 0.5, y: -10 },
}}
className="text-4xl font-light font-lexend text-center"
+ id="price-card"
>
Preço{" "}
atual