diff --git a/next/components/molecules/uiUserPage.js b/next/components/molecules/uiUserPage.js index 782f758f2..e59a4291b 100644 --- a/next/components/molecules/uiUserPage.js +++ b/next/components/molecules/uiUserPage.js @@ -94,7 +94,7 @@ export function ModalGeneral ({ minWidth={isMobileMod() ? "auto" : "536px"} boxSizing="content-box" padding="32px" - borderRadius="20px" + borderRadius="16px" {...propsModalContent} > diff --git a/next/pages/api/user/changeUserGcpEmail.js b/next/pages/api/user/changeUserGcpEmail.js new file mode 100644 index 000000000..4a247fd2f --- /dev/null +++ b/next/pages/api/user/changeUserGcpEmail.js @@ -0,0 +1,44 @@ +import axios from "axios"; + +const API_URL= `${process.env.NEXT_PUBLIC_API_URL}/api/v1/graphql` + +async function changeUserGcpEmail(email, token) { + try { + const res = await axios({ + url: API_URL, + method: "POST", + headers: { + Authorization: `Bearer ${token}` + }, + data: { + query: ` + mutation { + changeUserGcpEmail (email: "${email}"){ + ok + errors + } + } + ` + } + }) + const data = res.data?.data?.changeUserGcpEmail + return data + } catch (error) { + console.error(error) + return "err" + } +} + +export default async function handler(req, res) { + const token = () => { + if(req.query.q) return atob(req.query.q) + return req.cookies.token + } + + const result = await changeUserGcpEmail(atob(req.query.p), 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) +} diff --git a/next/pages/api/user/getUser.js b/next/pages/api/user/getUser.js index d9749b807..b38d3de83 100644 --- a/next/pages/api/user/getUser.js +++ b/next/pages/api/user/getUser.js @@ -20,6 +20,7 @@ async function getUser(id, token) { isAdmin isActive isEmailVisible + gcpEmail picture username firstName diff --git a/next/pages/api/user/isEmailInGoogleGroup.js b/next/pages/api/user/isEmailInGoogleGroup.js new file mode 100644 index 000000000..31472609a --- /dev/null +++ b/next/pages/api/user/isEmailInGoogleGroup.js @@ -0,0 +1,44 @@ +import axios from "axios"; + +const API_URL= `${process.env.NEXT_PUBLIC_API_URL}/api/v1/graphql` + +async function isEmailInGoogleGroup(email, token) { + try { + const res = await axios({ + url: API_URL, + method: "POST", + headers: { + Authorization: `Bearer ${token}` + }, + data: { + query: ` + query { + isEmailInGoogleGroup (email: "${email}"){ + ok + errors + } + } + ` + } + }) + const data = res.data?.data?.isEmailInGoogleGroup + return data + } catch (error) { + console.error(error) + return "err" + } +} + +export default async function handler(req, res) { + const token = () => { + if(req.query.q) return atob(req.query.q) + return req.cookies.token + } + + const result = await isEmailInGoogleGroup(atob(req.query.p), 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) +} diff --git a/next/pages/user/[username].js b/next/pages/user/[username].js index edcdd16bc..2555fbe24 100644 --- a/next/pages/user/[username].js +++ b/next/pages/user/[username].js @@ -733,7 +733,7 @@ const Account = ({ userInfo }) => { fontSize="14px" top="24px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} onClick={() => { setEmailSent(false) emailModal.onClose() @@ -749,7 +749,7 @@ const Account = ({ userInfo }) => { fontSize="14px" top="34px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> } @@ -888,7 +888,7 @@ instruções enviadas no e-mail para completar a alteração. @@ -1190,7 +1190,7 @@ const NewPassword = ({ userInfo }) => { fontSize="14px" top="34px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -1448,12 +1448,20 @@ const PlansAndPayment = ({ userData }) => { const [couponInfos, setCouponInfos] = useState({}) const [couponInputFocus, setCouponInputFocus] = useState(false) const [coupon, setCoupon] = useState("") + const [hasOpenEmailModal, setHasOpenEmailModal] = useState(false) + const [emailGCP, setEmailGCP] = useState(userData?.gcpEmail || userData?.email) + const [emailGCPFocus, setEmailGCPFocus] = useState(false) + const [errEmailGCP, setErrEmailGCP] = useState(false) + const [isLoadingEmailChange, setIsLoadingEmailChange] = useState(false) + const PaymentModal = useDisclosure() + const EmailModal = useDisclosure() const SucessPaymentModal = useDisclosure() const ErroPaymentModal = useDisclosure() const PlansModal = useDisclosure() const CancelModalPlan = useDisclosure() const AlertChangePlanModal = useDisclosure() + const [isLoading, setIsLoading] = useState(false) const [isLoadingH, setIsLoadingH] = useState(false) const [isLoadingCanSub, setIsLoadingCanSub] = useState(false) @@ -1518,7 +1526,10 @@ const PlansAndPayment = ({ userData }) => { const value = Object.values(plans).find(elm => elm._id === plan) if(value?.interval === "month") setToggleAnual(false) setCheckoutInfos(value) - PaymentModal.onOpen() + if(!hasOpenEmailModal) { + EmailModal.onOpen() + setHasOpenEmailModal(true) + } }, [plan, plans]) useEffect(() => { @@ -1800,6 +1811,28 @@ const PlansAndPayment = ({ userData }) => { ) } + async function handlerEmailGcp() { + setErrEmailGCP(false) + setIsLoadingEmailChange(true) + + function isValidEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) + } + if(!isValidEmail(emailGCP)) return setErrEmailGCP(true) + + const response = await fetch(`/api/user/changeUserGcpEmail?p=${btoa(emailGCP)}`) + .then(res => res.json()) + + if(response.ok) { + setIsLoadingEmailChange(false) + EmailModal.onClose() + PaymentModal.onOpen() + } else { + setErrEmailGCP(true) + } + } + useEffect(() => { if(valueCoupon === "") { setCoupon("") @@ -1834,6 +1867,16 @@ const PlansAndPayment = ({ userData }) => { isCentered={isMobileMod() ? false : true} > + + Passo 2 de 2 + { fontSize="14px" top="34px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -1903,29 +1946,36 @@ const PlansAndPayment = ({ userData }) => { - {toggleAnual ? - changeIntervalPlanCheckout()} - /> - : - changeIntervalPlanCheckout()} - /> - } - Desconto anual + + {toggleAnual ? + changeIntervalPlanCheckout()} + /> + : + changeIntervalPlanCheckout()} + /> + } + Desconto anual + { @@ -1999,7 +2049,7 @@ const PlansAndPayment = ({ userData }) => { display="flex" alignItems="center" justifyContent="center" - width="fit-content" + width={{base: "100%", lg: "fit-content"}} height="44px" borderRadius="8px" padding="10px 34px" @@ -2090,6 +2140,39 @@ const PlansAndPayment = ({ userData }) => { 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)}. } + + + { + PaymentModal.onClose() + EmailModal.onOpen() + }} + > + Voltar + + @@ -2109,6 +2192,224 @@ const PlansAndPayment = ({ userData }) => { onSucess={() => openModalSucess()} onErro={() => openModalErro()} /> + + + { + PaymentModal.onClose() + EmailModal.onOpen() + }} + > + Voltar + + + + + + + {/* email gcp */} + { + setEmailGCP(userData?.gcpEmail || userData?.email) + setErrEmailGCP(false) + EmailModal.onClose() + }} + propsModalContent={{ + width: "100%", + maxWidth:"1008px", + margin: "24px", + }} + isCentered={isMobileMod() ? false : true} + > + + + Passo 1 de 2 + + + + + + + E-mail de acesso ao BigQuery + + + + O seu e-mail precisa ser uma Conta Google para garantir acesso exclusivo aos dados pelo BigQuery. Já preenchemos com o e-mail que você usou ao criar sua conta na nossa plataforma. Caso necessite usar outro e-mail para acessar o BigQuery, basta editá-lo abaixo. + + + + E-mail de acesso + + + + + + + {errEmailGCP && + + Por favor, insira um e-mail válido. + + } + + + + { + setEmailGCP(userData?.gcpEmail || userData?.email) + setErrEmailGCP(false) + EmailModal.onClose() + + }} + > + Cancelar + + + handlerEmailGcp()} + > + {isLoadingEmailChange ? + + : + "Próximo" + } @@ -2116,6 +2417,10 @@ const PlansAndPayment = ({ userData }) => { {/* success */} setIsLoading(true)} > @@ -2124,7 +2429,7 @@ const PlansAndPayment = ({ userData }) => { fontSize="14px" top="28px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -2145,24 +2450,23 @@ const PlansAndPayment = ({ userData }) => { fill="#34A15A" /> - Parabéns! + Assinatura efetuada com sucesso! - Seu pagamento foi efetuado com sucesso e seu plano foi atualizado. + O acesso aos dados foi concedido para o e-mail {emailGCP}. Se precisar alterar o e-mail de acesso, você pode fazer isso na seção “BigQuery” das configurações da sua conta. + Em caso de dúvida, entre em contato com nosso suporte. @@ -2172,26 +2476,61 @@ const PlansAndPayment = ({ userData }) => { gap="24px" width="100%" > - setIsLoading(true)} + color="#2B8C4D" + borderColor="#2B8C4D" + _hover={{ + borderColor: "#22703E", + color: "#22703E" + }} + fontFamily="Roboto" + fontWeight="500" + fontSize="14px" + lineHeight="20px" + onClick={() => window.open(`/user/${userData?.username}?big_query`, "_self")} > {isLoading ? : - "Continuar nas configurações" + "Alterar e-mail de acesso" } - + - setIsLoadingH(true)} > {isLoadingH ? @@ -2199,7 +2538,7 @@ const PlansAndPayment = ({ userData }) => { : "Ir para a página inicial" } - + @@ -2214,7 +2553,7 @@ const PlansAndPayment = ({ userData }) => { fontSize="14px" top="28px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -2318,7 +2657,7 @@ const PlansAndPayment = ({ userData }) => { fontSize="14px" top="34px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -2416,7 +2755,7 @@ const PlansAndPayment = ({ userData }) => { onClick: subscriptionInfo?.stripeSubscription === "bd_pro" ? () => {} : () => { setPlan(plans?.[`bd_pro_${toggleAnual ? "year" : "month"}`]._id) PlansModal.onClose() - PaymentModal.onOpen() + EmailModal.onOpen() }, isCurrentPlan: subscriptionInfo?.stripeSubscription === "bd_pro" ? true : false, }} @@ -2437,7 +2776,7 @@ const PlansAndPayment = ({ userData }) => { onClick: subscriptionInfo?.stripeSubscription === "bd_pro_empresas" ? () => {} : () => { setPlan(plans?.[`bd_empresas_${toggleAnual ? "year" : "month"}`]._id) PlansModal.onClose() - PaymentModal.onOpen() + EmailModal.onOpen() }, isCurrentPlan: subscriptionInfo?.stripeSubscription === "bd_pro_empresas" ? true : false, }} @@ -2464,7 +2803,7 @@ const PlansAndPayment = ({ userData }) => { fontSize="14px" top="34px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -2514,7 +2853,7 @@ const PlansAndPayment = ({ userData }) => { fontSize="14px" top="34px" right="26px" - _hover={{backgroundColor: "transparent", color:"#42B0FF"}} + _hover={{backgroundColor: "transparent", opacity: 0.7}} /> @@ -2864,7 +3203,118 @@ const Accesses = ({ userInfo }) => { ) } -// Sections Of User Page + +const BigQuery = ({ userInfo }) => { + const [emailGcp, setEmailGcp] = useState(userInfo?.gcpEmail || userInfo?.email) + const [errors, setErrors] = useState({}) + const [isLoading, setIsLoading] = useState(false) + + async function handleUpdateEmailGcp() { + setErrors({}) + setIsLoading(true) + + function isValidEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) + } + + if(!isValidEmail(emailGcp)) { + setErrors({emailGcp: "Por favor, insira um e-mail válido."}) + } else { + const reg = new RegExp("(?<=:).*") + const [ id ] = reg.exec(userInfo.id) + + let user + let attempts = 0 + const maxAttempts = 10 + const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + + const response = await fetch(`/api/user/changeUserGcpEmail?p=${btoa(emailGcp)}`) + .then(res => res.json()) + + if(response.ok) { + while (!user?.gcpEmail && attempts < maxAttempts) { + user = await fetch(`/api/user/getUser?p=${btoa(id)}`, { method: "GET" }) + .then((res) => res.json()) + + if (user?.gcpEmail) { + cookies.set("userBD", JSON.stringify(user)) + break + } + + attempts++ + await delay(10000) + } + } else { + setErrors({emailGcp: "Por favor, insira um e-mail válido."}) + } + } + setIsLoading(false) + } + + return ( + + + + + E-mail de acesso ao BigQuery + + + + O seu e-mail precisa ser uma Conta Google para garantir acesso exclusivo aos dados pelo BigQuery. + + + + setEmailGcp(e.target.value)} + placeholder="Insira o e-mail que deseja utilizar para acessar o BigQuery" + fontFamily="ubuntu" + + maxWidth="480px" + height="40px" + fontSize="14px" + borderRadius="16px" + _invalid={{boxShadow:"0 0 0 2px #D93B3B"}} + /> + + {errors.emailGcp} + + + + handleUpdateEmailGcp()} + isDisabled={isLoading} + > + {isLoading ? + + : + "Atualizar e-mail" + } + + + ) +} export default function UserPage({ getUser }) { const router = useRouter() @@ -2876,11 +3326,17 @@ export default function UserPage({ getUser }) { setUserInfo(getUser) }, [getUser]) + const isUserPro = () => { + if(getUser?.internalSubscription?.edges?.[0]?.node?.isActive === true) return true + return false + } + const choices = [ {bar: "Perfil público", title: "Perfil público", value: "profile", index: 0}, {bar: "Conta", title: "Conta", value: "account", index: 1}, {bar: "Senha", title: "Alterar senha", value: "new_password", index: 2}, {bar: "Planos e pagamento", title: "Planos e pagamento", value: "plans_and_payment", index: 3}, + isUserPro() && {bar: "BigQuery", title: "BigQuery", value: "big_query", index: 4}, ] // {bar: "Acessos", title: "Gerenciar acessos", value: "accesses", index: 4}, @@ -2961,6 +3417,7 @@ export default function UserPage({ getUser }) { {sectionSelected === 1 && } {sectionSelected === 2 && } {sectionSelected === 3 && } + {sectionSelected === 4 && isUserPro() && } {/* {sectionSelected === 4 && } */} diff --git a/next/public/img/icons/penIcon.js b/next/public/img/icons/penIcon.js index 64f6af984..bd061b1fa 100644 --- a/next/public/img/icons/penIcon.js +++ b/next/public/img/icons/penIcon.js @@ -2,11 +2,11 @@ import { createIcon } from '@chakra-ui/icons'; const PenIcon = createIcon({ displayName: "pen", - viewBox: "0 0 22 22", + viewBox: "0 0 32 32", path: ( ) })