diff --git a/next/components/molecules/ColumnsTable.js b/next/components/molecules/ColumnsTable.js index cbe2e726..d507e95b 100644 --- a/next/components/molecules/ColumnsTable.js +++ b/next/components/molecules/ColumnsTable.js @@ -440,7 +440,7 @@ export default function ColumnsTable({ + } {headers.map((elm, i) => ( @@ -594,7 +604,9 @@ export default function ColumnsTable({ {template === "checks" && handleCheckboxChange(elm.node.name)} /> + } {elm?.node?.name ? elm.node.name : "Não informado"} - }/> + {/* }/> */} + }/> }/> }/> }/> diff --git a/next/components/organisms/DatasetResource.js b/next/components/organisms/DatasetResource.js index 6d1f4c90..d9c22ebc 100644 --- a/next/components/organisms/DatasetResource.js +++ b/next/components/organisms/DatasetResource.js @@ -23,7 +23,7 @@ export default function DatasetResource({ const [informationRequests, setInformationRequests] = useState([]) const pushQuery = (key, value) => { - router.push({ + router.replace({ pathname: `/dataset/${query.dataset}`, query: { [key]: value } }, diff --git a/next/components/organisms/PaymentSystem.js b/next/components/organisms/PaymentSystem.js index 64f6bc98..9a7927d4 100644 --- a/next/components/organisms/PaymentSystem.js +++ b/next/components/organisms/PaymentSystem.js @@ -53,6 +53,7 @@ const PaymentForm = ({ onSucess, onErro, clientSecret}) => { return (
{
) } -export default function PaymentSystem({ userData, plan, onSucess, onErro }) { +export default function PaymentSystem({ userData, plan, coupon, onSucess, onErro }) { const [clientSecret, setClientSecret] = useState("") const appearance = { theme: "stripe", variables: { + fontFamily: 'Roboto, sans-serif', fontSizeBase: "16px", fontSizeSm: "16px", - fontFamily: "Ubuntu", borderRadius: "14px", - colorPrimary: "#42B0FF", - colorTextPlaceholder: "#A3A3A3", - colorDanger: "#D93B3B", - colorBackground: "#FFF", + colorPrimary: "#2B8C4D", + colorTextPlaceholder: "#464A51", + colorDanger: "#BF3434", + colorBackground: "#FFFFFF", colorText: "#252A32", }, rules: { ".Input": { - border: "1px solid #DEDFE0", + borderRadius: "8px", + border: "2px solid #EEEEEE", + backgroundColor: "#EEEEEE" }, ".Input:hover": { - border: "2px solid #42B0FF", + backgroundColor:"#DEDFE0", + borderColor: "#DEDFE0" }, + ".Input:focus": { + backgroundColor: "#FFFFFF", + border:"2px solid #0068C5", + borderColor: "#0068C5", + boxShadow: "none", + outline: "none" + }, + ".Input:focus:hover": { + backgroundColor: "#FFFFFF", + borderColor: "#0068C5", + }, + ".Tab": { + border: "2px solid #ececec", + backgroundColor: "#FFF", + boxShadow: "none" + }, + ".Tab:focus": { + boxShadow: "none" + }, + ".Tab--selected": { + boxShadow: "none" + }, + ".Tab--selected:focus": { + boxShadow: "none" + } } } const options = { clientSecret, appearance, - fonts: [{ cssSrc: 'https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;700&display=swap' }], + fonts: [{ cssSrc: 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap' }], } - const customerCreatPost = async (id) => { - const clientSecret = await fetch(`/api/stripe/createSubscription?p=${btoa(id)}`, {method: "GET"}) + const customerCreatPost = async (id, coupon) => { + const clientSecret = await fetch(`/api/stripe/createSubscription?p=${btoa(id)}&c=${btoa(coupon)}`, {method: "GET"}) .then(res => res.json()) if (clientSecret) { @@ -125,8 +153,9 @@ export default function PaymentSystem({ userData, plan, onSucess, onErro }) { } useEffect(() => { - customerCreatPost(plan.id) - }, []) + setClientSecret("") + customerCreatPost(plan, coupon) + }, [plan, coupon]) const SkeletonBox = ({ type, ...props }) => { if(type === "text") return @@ -136,7 +165,7 @@ export default function PaymentSystem({ userData, plan, onSucess, onErro }) { } if(!clientSecret) return ( - + diff --git a/next/pages/api/stripe/createSubscription.js b/next/pages/api/stripe/createSubscription.js index 814e1d07..ac4d4f44 100644 --- a/next/pages/api/stripe/createSubscription.js +++ b/next/pages/api/stripe/createSubscription.js @@ -2,7 +2,24 @@ import axios from "axios"; const API_URL= `${process.env.NEXT_PUBLIC_API_URL}/api/v1/graphql` -async function createSubscription(id, token) { +async function createSubscription({id, coupon, token}) { + const query = coupon !== "" ? + ` + mutation { + createStripeSubscription (priceId: ${id}, coupon: "${coupon}") { + clientSecret + } + } + ` + : + ` + mutation { + createStripeSubscription (priceId: ${id}) { + clientSecret + } + } + ` + try { const res = await axios({ url: API_URL, @@ -11,13 +28,7 @@ async function createSubscription(id, token) { Authorization: `Bearer ${token}` }, data: { - query: ` - mutation { - createStripeSubscription (priceId: ${id}) { - clientSecret - } - } - ` + query: query } }) const data = res.data @@ -30,7 +41,11 @@ async function createSubscription(id, token) { export default async function handler(req, res) { const token = req.cookies.token - const result = await createSubscription(atob(req.query.p), token) + const result = await createSubscription({ + id: atob(req.query.p), + coupon: atob(req.query.c), + token: token + }) if(result.errors) return res.status(500).json({error: result.errors}) if(result === "err") return res.status(500).json({error: "err"}) diff --git a/next/pages/api/stripe/validateStripeCoupon.js b/next/pages/api/stripe/validateStripeCoupon.js new file mode 100644 index 00000000..178c465e --- /dev/null +++ b/next/pages/api/stripe/validateStripeCoupon.js @@ -0,0 +1,47 @@ +import axios from "axios"; + +const API_URL= `${process.env.NEXT_PUBLIC_API_URL}/api/v1/graphql` + +async function validateStripeCoupon({id, coupon, token}) { + try { + const res = await axios({ + url: API_URL, + method: "POST", + headers: { + Authorization: `Bearer ${token}` + }, + data: { + query:` + mutation { + validateStripeCoupon (priceId: ${id}, coupon: "${coupon}") { + isValid + discountAmount + duration + durationInMonths + errors + } + } + ` + } + }) + const data = res.data + return data + } catch (error) { + console.error(error) + return "err" + } +} + +export default async function handler(req, res) { + const token = req.cookies.token + const result = await validateStripeCoupon({ + id: atob(req.query.p), + coupon: atob(req.query.c), + token: token + }) + + if(result.errors) return res.status(500).json({error: result.errors}) + if(result === "err") return res.status(500).json({error: "err"}) + + res.status(200).json(result?.data?.validateStripeCoupon) +} diff --git a/next/pages/precos.js b/next/pages/precos.js index 8a89bc4c..2a7d517e 100644 --- a/next/pages/precos.js +++ b/next/pages/precos.js @@ -85,7 +85,7 @@ export const CardPrice = ({ Leia os @@ -453,7 +454,7 @@ export function SectionPrice() { ]} button={{ text: isBDEmp.isCurrentPlan && planIntervalPlan() ? "Plano atual" : hasSubscribed ? "Assinar" : "Iniciar teste grátis", - href: username === null ? `/user/login?q=empresas&i=${plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id}` :`/user/${username}?plans_and_payment&q=empresas&i=${plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id}`, + href: username === null ? `/user/login?i=${plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id}` :`/user/${username}?plans_and_payment&i=${plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id}`, isCurrentPlan: isBDEmp.isCurrentPlan && planIntervalPlan(), }} /> diff --git a/next/pages/quem-somos.js b/next/pages/quem-somos.js index 5dfa81dc..e6538000 100755 --- a/next/pages/quem-somos.js +++ b/next/pages/quem-somos.js @@ -27,6 +27,7 @@ import InternalError from "../public/img/internalError"; import WebIcon from "../public/img/icons/webIcon"; import EmailIcon from "../public/img/icons/emailIcon"; import TwitterIcon from "../public/img/icons/twitterIcon"; +import BlueskyIcon from "../public/img/icons/blueskyIcon"; import LinkedinIcon from "../public/img/icons/linkedinIcon"; import GithubIcon from "../public/img/icons/githubIcon"; import DiscordIcon from "../public/img/icons/discordIcon"; @@ -376,7 +377,8 @@ export default function QuemSomos({ data }) { zIndex="1" backgroundColor="#FFF" > - + {/* */} + diff --git a/next/pages/user/[username].js b/next/pages/user/[username].js index 3e062cbc..00638d74 100644 --- a/next/pages/user/[username].js +++ b/next/pages/user/[username].js @@ -32,6 +32,7 @@ import cookies from 'js-cookie'; import { serialize } from 'cookie'; import { MainPageTemplate } from "../../components/templates/main"; import { isMobileMod } from "../../hooks/useCheckMobile.hook"; +import { ControlledInputSimple } from "../../components/atoms/ControlledInput"; import Checkbox from "../../components/atoms/Checkbox"; import BigTitle from "../../components/atoms/BigTitle"; import SectionTitle from "../../components/atoms/SectionTitle"; @@ -1440,7 +1441,13 @@ const PlansAndPayment = ({ userData }) => { const router = useRouter() const { query } = router - const [plan, setPlan] = useState({}) + const [plan, setPlan] = useState("") + const [checkoutInfos, setCheckoutInfos] = useState({}) + const [valueCoupon, setValueCoupon] = useState("") + const [errCoupon, setErrCoupon] = useState(false) + const [couponInfos, setCouponInfos] = useState({}) + const [couponInputFocus, setCouponInputFocus] = useState(false) + const [coupon, setCoupon] = useState("") const PaymentModal = useDisclosure() const SucessPaymentModal = useDisclosure() const ErroPaymentModal = useDisclosure() @@ -1471,8 +1478,6 @@ const PlansAndPayment = ({ userData }) => { }, [userData?.id]) useEffect(() => { - if(PlansModal.isOpen === false) return - async function fecthPlans() { try { const result = await fetch(`/api/stripe/getPlans`, { method: "GET" }) @@ -1504,13 +1509,22 @@ const PlansAndPayment = ({ userData }) => { } fecthPlans() - }, [PlansModal.isOpen]) + }, []) + + useEffect(() => { + if(plans === null) return + if(plan === "") return + + const value = Object.values(plans).find(elm => elm._id === plan) + if(value?.interval === "month") setToggleAnual(false) + setCheckoutInfos(value) + PaymentModal.onOpen() + }, [plan, plans]) useEffect(() => { if(query.i) { if(subscriptionInfo?.isActive === true) return AlertChangePlanModal.onOpen() - setPlan({id: query.i}) - PaymentModal.onOpen() + setPlan(query.i) } }, [query]) @@ -1521,7 +1535,10 @@ const PlansAndPayment = ({ userData }) => { title: "BD Grátis", buttons: [{ text:"Comparar planos", - onClick: () => PlansModal.onOpen()} + onClick: () => { + PlansModal.onOpen() + setToggleAnual(true) + }} ], resources : [ {name: "Tabelas tratadas"}, @@ -1689,11 +1706,92 @@ const PlansAndPayment = ({ userData }) => { return formattedDate } - function formattedPlanInterval (value) { - if(value === "month") return "(Mensal)" - if(value === "year") return "(Anual)" + function formattedPlanInterval (value, variant = false) { + if(variant) { + if(value === "month") return "mês" + if(value === "year") return "ano" + } else { + if(value === "month") return "(Mensal)" + if(value === "year") return "(Anual)" + } + } + + function changeIntervalPlanCheckout() { + let togglerValue = !toggleAnual ? "year" : "month" + const value = Object.values(plans).find(elm => elm.interval === togglerValue && elm.productSlug === checkoutInfos?.productSlug) + setCheckoutInfos(value) + setCoupon("") + setValueCoupon("") + setPlan(value._id) + setToggleAnual(!toggleAnual) + } + + async function validateStripeCoupon() { + if(valueCoupon === "") return + setErrCoupon(false) + + const result = await fetch(`/api/stripe/validateStripeCoupon?p=${btoa(plan)}&c=${btoa(valueCoupon)}`, { method: "GET" }) + .then(res => res.json()) + + if(result?.isValid === false || result?.errors || !result) { + setValueCoupon("") + setErrCoupon(true) + } + if(result?.duration === "repeating" && toggleAnual === true) { + setValueCoupon("") + setErrCoupon(true) + } else { + setCouponInfos(result) + setCoupon(valueCoupon) + } + } + + const CouponDisplay = () => { + let limitText + + if(couponInfos?.duration === "once") limitText = toggleAnual ? "(válido por 1 ano)" : "(válido por 1 mês)" + if(couponInfos?.duration === "repeating") limitText = `(válido por ${couponInfos?.durationInMonths} ${couponInfos?.durationInMonths.length === 1 ? "mês" : "meses"})` + + return ( + <> + + Cupom {coupon.toUpperCase()} {limitText} + + + - {couponInfos?.discountAmount?.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 2 })}/{formattedPlanInterval(checkoutInfos?.interval, true)} + + + ) + } + + const TotalToPayDisplay = () => { + let value + + if(couponInfos?.discountAmount) { + value = (checkoutInfos?.amount-couponInfos?.discountAmount).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 2 }) + } else { + value = checkoutInfos?.amount?.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 2 }) + } + + return ( + <> + + Total a pagar + + + {value}/{formattedPlanInterval(checkoutInfos?.interval, true)} + + + ) } + useEffect(() => { + if(valueCoupon === "") { + setCoupon("") + setCouponInfos("") + } + }, [valueCoupon]) + useEffect(() => { if(isLoading === true || isLoadingH === true) closeModalSucess() if(isLoadingCanSub === true) cancelSubscripetion() @@ -1703,22 +1801,34 @@ const PlansAndPayment = ({ userData }) => { + {/* stripe */} { + setToggleAnual(true) + setValueCoupon("") + if(query.i) return window.open(`/user/${userData.username}?plans_and_payment`, "_self") + PaymentModal.onClose() + }} propsModalContent={{ - minWidth: "fit-content", - maxWidth: "fit-content", - maxHeight: "fit-content", - margin: "24px 0" + width: "100%", + maxWidth:"1008px", + margin: "24px" }} + isCentered={isMobileMod() ? false : true} > - - - Assinatura {plan.title} - + + + Pagamento + { - openModalSucess()} - onErro={() => openModalErro()} - /> + + + + + {checkoutInfos?.productName} + + { + PaymentModal.onClose() + setToggleAnual(true) + setCoupon("") + setValueCoupon("") + PlansModal.onOpen() + }} + >Alterar plano + + + + {toggleAnual ? + changeIntervalPlanCheckout()} + /> + : + changeIntervalPlanCheckout()} + /> + } + Desconto anual + Economize 20% + + + + + + Cupom de desconto + + + + + + {valueCoupon && + setValueCoupon("")} + /> + } + + + validateStripeCoupon()} + > + Aplicar + + + + {errCoupon && + + Por favor, insira um cupom válido. + + } + + + Período de teste - 7 dias grátis + + + + + + Subtotal + + + {checkoutInfos?.amount?.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 2 })}/{formattedPlanInterval(checkoutInfos?.interval, true)} + + + {couponInfos?.discountAmount && + + } + + + + {(couponInfos?.duration === "once" || couponInfos?.duration === "repeating") && + + A partir do {couponInfos?.duration === "once" && 2} {couponInfos?.duration === "repeating" && couponInfos?.durationInMonths + 1}º {formattedPlanInterval(checkoutInfos?.interval, true)} {!hasSubscribed && "e 7º dia"}, o total a pagar será de {checkoutInfos?.amount?.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', minimumFractionDigits: 2 })}/{formattedPlanInterval(checkoutInfos?.interval, true)}. + + } + + + + + Detalhes do pagamento + + openModalSucess()} + onErro={() => openModalErro()} + /> + + {/* success */} setIsLoading(true)} @@ -1833,6 +2188,7 @@ const PlansAndPayment = ({ userData }) => { + {/* err */} { + {/* modal plans */} { - PlansModal.onClose() - setToggleAnual(true) - }} + onClose={PlansModal.onClose} propsModal={{ scrollBehavior: isMobileMod() ? "outside" : "inside", }} @@ -1928,19 +2282,20 @@ const PlansAndPayment = ({ userData }) => { minWidth: "fit-content", maxHeight: "fit-content", margin: isMobileMod() ? "0" : "24px", + padding: "32px 22px 26px 22px", borderRadius: isMobileMod() ? "0" : "20px", }} isCentered={isMobileMod() ? false : true} > - + Compare os planos @@ -2003,10 +2358,10 @@ const PlansAndPayment = ({ userData }) => { gridTemplateColumns="repeat(3, 320px)" gridTemplateRows="1fr" alignItems={isMobileMod() ? "center" : {base: "center", lg: "inherit"}} - padding="0 20px 10px" + padding="0 10px 6px" justifyContent="center" justifyItems="center" - gap="20px" + gap="24px" spacing={0} > { button={{ text: `${subscriptionInfo?.stripeSubscription === "bd_pro" ? "Plano atual" : hasSubscribed ? "Assinar" : "Iniciar teste grátis"}`, onClick: subscriptionInfo?.stripeSubscription === "bd_pro" ? () => {} : () => { - setPlan({id: plans?.[`bd_pro_${toggleAnual ? "year" : "month"}`]._id}) + setPlan(plans?.[`bd_pro_${toggleAnual ? "year" : "month"}`]._id) PlansModal.onClose() PaymentModal.onOpen() }, @@ -2064,7 +2419,7 @@ const PlansAndPayment = ({ userData }) => { button={{ text: `${subscriptionInfo?.stripeSubscription === "bd_pro_empresas" ? "Plano atual" : hasSubscribed ? "Assinar" : "Iniciar teste grátis"}`, onClick: subscriptionInfo?.stripeSubscription === "bd_pro_empresas" ? () => {} : () => { - setPlan({id: plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id}) + setPlan(plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id) PlansModal.onClose() PaymentModal.onOpen() }, @@ -2075,6 +2430,7 @@ const PlansAndPayment = ({ userData }) => {
+ {/* err plans */} { + {/* cancel */} { letterSpacing="0.3px" _hover={{opacity: 0.7}} marginTop="16px !important" - onClick={() => PlansModal.onOpen()} + onClick={() => { + PlansModal.onOpen() + setToggleAnual(true) + }} > Veja tudo e compare os planos diff --git a/next/pages/user/login.js b/next/pages/user/login.js index a6c30d19..24ad958c 100644 --- a/next/pages/user/login.js +++ b/next/pages/user/login.js @@ -82,7 +82,7 @@ export default function Login() { cookies.set('userBD', JSON.stringify(userData)) if(query.i) { - return window.open(`/user/${userData.username}?plans_and_payment&q=${query.q}&i=${query.i}`, "_self") + return window.open(`/user/${userData.username}?plans_and_payment&i=${query.i}`, "_self") } if(userData.workDataTool === null) return window.open("/user/survey", "_self") diff --git a/next/public/img/icons/blueskyIcon.js b/next/public/img/icons/blueskyIcon.js new file mode 100644 index 00000000..25975b01 --- /dev/null +++ b/next/public/img/icons/blueskyIcon.js @@ -0,0 +1,14 @@ +import { createIcon } from '@chakra-ui/icons'; + +const BlueskyIcon = createIcon({ + displayName: "bluesky", + viewBox: "0 0 24 24", + path: ( + + ), +}) + +export default BlueskyIcon \ No newline at end of file diff --git a/next/styles/paymentSystem.module.css b/next/styles/paymentSystem.module.css index ed2f31bc..609e8a37 100644 --- a/next/styles/paymentSystem.module.css +++ b/next/styles/paymentSystem.module.css @@ -6,18 +6,10 @@ } .modal { - width: 400px; height: 100%; scroll-margin-left: 20px; } -@media (max-width: 600px) { - .modal { - overflow-y: initial; - width: 400px; - } -} - .modal::-webkit-scrollbar { width: 4px; height: 4px;