Skip to content

Commit 43e021c

Browse files
authored
Merge pull request #1586 from basedosdados/feat/trial-chatbot
Feat: Trial chatbot wip Staging
2 parents 2adbe7c + 4765a95 commit 43e021c

12 files changed

Lines changed: 202 additions & 73 deletions

File tree

next/components/organisms/PaymentSystem.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ export default function PaymentSystem({
9090
}) {
9191
const [clientSecret, setClientSecret] = useState("")
9292

93+
useEffect(() => {
94+
if (clientSecret === null) onSucess()
95+
}, [clientSecret])
96+
9397
const appearance = {
9498
theme: "stripe",
9599
variables: {
@@ -148,6 +152,14 @@ export default function PaymentSystem({
148152
}
149153

150154
const customerCreatPost = async (id, coupon) => {
155+
const trial = await fetch(`/api/stripe/startChatbotTrial?p=${btoa(id)}`, {method: "GET"})
156+
.then(res => res.json())
157+
158+
if (trial?.started) {
159+
setClientSecret(null)
160+
return isLoading(false)
161+
}
162+
151163
const clientSecret = await fetch(`/api/stripe/createSubscription?p=${btoa(id)}&c=${btoa(coupon)}`, {method: "GET"})
152164
.then(res => res.json())
153165

@@ -172,7 +184,9 @@ export default function PaymentSystem({
172184
if(type === "bnt") return <Skeleton height="40px" borderRadius="12px" startColor="#F0F0F0" endColor="#F3F3F3" {...props}/>
173185
}
174186

175-
if(!clientSecret) return (
187+
if(clientSecret === null) return null
188+
189+
if(clientSecret === "") return (
176190
<Stack flex={1}>
177191
<Stack width="100%" flexDirection="row" spacing={0} gap="8px" marginBottom="16px !important">
178192
<Stack width="100%" spacing={0} gap="8px">

next/components/organisms/componentsUserPage/PlansAndPayment.js

Lines changed: 80 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,36 @@ import BodyText from "../../atoms/Text/BodyText";
2323
import Toggle from "../../atoms/Toggle";
2424
import { SectionPrice } from "../../../pages/prices";
2525
import PaymentSystem from "../../organisms/PaymentSystem";
26-
import { triggerGAEvent, triggerGAEventWithData, hasBDProSubscription, hasChatbotSubscription, getChatbotStreamlitAppUrl } from "../../../utils";
26+
import { triggerGAEvent, triggerGAEventWithData, hasBDProSubscription, hasChatbotSubscription, getChatbotStreamlitAppUrl, getSubscriptionStatusKey, isSubscriptionTrialing } from "../../../utils";
27+
28+
const SubscriptionBadgeStyles = {
29+
active: { backgroundColor: "#D5E8DB", color: "#2B8C4D" },
30+
canceled: { backgroundColor: "#F6E3E3", color: "#BF3434" },
31+
trial: { backgroundColor: "#E8F2FC", color: "#0068C5" },
32+
}
33+
34+
function SubscriptionStatusBadge({ subscription, t, fallbackStatus = "active" }) {
35+
const statusKey = subscription ? getSubscriptionStatusKey(subscription) : fallbackStatus
36+
const badgeStyle = SubscriptionBadgeStyles[statusKey] || SubscriptionBadgeStyles.active;
37+
38+
return (
39+
<Badge
40+
width="fit-content"
41+
padding="2px 4px"
42+
textTransform="none"
43+
borderRadius="6px"
44+
backgroundColor={badgeStyle.backgroundColor}
45+
color={badgeStyle.color}
46+
fontSize="12px"
47+
lineHeight="18px"
48+
fontFamily="Roboto"
49+
fontWeight="500"
50+
letterSpacing="0.1px"
51+
>
52+
{t(`username.${statusKey}`)}
53+
</Badge>
54+
)
55+
}
2756

2857
import {
2958
TitleTextForm,
@@ -111,7 +140,9 @@ export default function PlansAndPayment ({ userData }) {
111140
const [isLoadingH, setIsLoadingH] = useState(false)
112141
const [isLoadingCanSub, setIsLoadingCanSub] = useState(false)
113142
const [isLoadingClientSecret, setIsLoadingClientSecret] = useState(true)
114-
const [hasSubscribed, setHasSubscribed] = useState(true)
143+
const [hasSubscribedBDPro, setHasSubscribedBDPro] = useState(true)
144+
const [hasSubscribedChatbot, setHasSubscribedChatbot] = useState(true)
145+
const [hasSubscribedLoaded, setHasSubscribedLoaded] = useState(false)
115146
const [plans, setPlans] = useState(null)
116147
const [toggleAnual, setToggleAnual] = useState(true)
117148
const [subscriptionToCancel, setSubscriptionToCancel] = useState("bd_pro")
@@ -129,16 +160,32 @@ export default function PlansAndPayment ({ userData }) {
129160
}) || null
130161

131162
async function alreadySubscribed(id) {
132-
const result = await fetch(`/api/user/getAlreadySubscribed?p=${btoa(id)}`)
133-
.then(res => res.json())
134-
setHasSubscribed(result)
163+
try {
164+
const [bdPro, chatbot] = await Promise.all([
165+
fetch(`/api/user/getAlreadySubscribed?p=${btoa(id)}&type=bd_pro`).then(res => res.json()),
166+
fetch(`/api/user/getAlreadySubscribed?p=${btoa(id)}&type=chatbot`).then(res => res.json()),
167+
])
168+
setHasSubscribedBDPro(bdPro)
169+
setHasSubscribedChatbot(chatbot)
170+
} catch (error) {
171+
console.error(error)
172+
setHasSubscribedBDPro(false)
173+
setHasSubscribedChatbot(false)
174+
} finally {
175+
setHasSubscribedLoaded(true)
176+
}
135177
}
136178

137179
useEffect(() => {
138180
const reg = new RegExp("(?<=:).*")
139-
const [ id ] = reg.exec(userData.id)
181+
const match = reg.exec(userData.id)
182+
183+
if (!match) {
184+
setHasSubscribedLoaded(true)
185+
return
186+
}
140187

141-
alreadySubscribed(id)
188+
alreadySubscribed(match[0])
142189
}, [userData?.id])
143190

144191
useEffect(() => {
@@ -193,6 +240,7 @@ export default function PlansAndPayment ({ userData }) {
193240
useEffect(() => {
194241
if(plans === null) return
195242
if(plan === "") return
243+
if(!hasSubscribedLoaded) return
196244

197245
const value = Object.values(plans).find(elm => elm?._id === plan)
198246
if (!value) return
@@ -217,12 +265,16 @@ export default function PlansAndPayment ({ userData }) {
217265
const checkoutAlreadyVisible = PaymentModal.isOpen || EmailModal.isOpen
218266
if (!checkoutAlreadyVisible) {
219267
if (isChatbotType) {
220-
openCheckoutPlanStep()
268+
if (!hasSubscribedChatbot) {
269+
openCheckoutPaymentStep()
270+
} else {
271+
openCheckoutPlanStep()
272+
}
221273
} else {
222274
EmailModal.onOpen()
223275
}
224276
}
225-
}, [plan, plans, userData, chatbotSubscriptionInfo])
277+
}, [plan, plans, userData, chatbotSubscriptionInfo, hasSubscribedLoaded, hasSubscribedChatbot])
226278

227279
useEffect(() => {
228280
if (!plans || plan !== "") return
@@ -273,7 +325,9 @@ export default function PlansAndPayment ({ userData }) {
273325

274326
const planActive = hasBDProSubscription(userData)
275327
const hasChatbotActiveSubscription = hasChatbotSubscription(userData)
328+
const isChatbotTrial = isSubscriptionTrialing(chatbotSubscriptionInfo)
276329
const isChatbotCheckout = checkoutInfos?.productName?.toLowerCase().includes("chatbot") || checkoutInfos?.productSlug?.toLowerCase().includes("chatbot")
330+
const hasSubscribed = isChatbotCheckout ? hasSubscribedChatbot : hasSubscribedBDPro
277331

278332
function getCheckoutStepLabel() {
279333
if (checkoutStep === "plan") {
@@ -297,6 +351,12 @@ export default function PlansAndPayment ({ userData }) {
297351
PaymentModal.onOpen()
298352
}
299353

354+
function openCheckoutPaymentStep() {
355+
setCheckoutStep("payment")
356+
setIsLoadingClientSecret(true)
357+
PaymentModal.onOpen()
358+
}
359+
300360
function goBackFromPlanStep() {
301361
PaymentModal.onClose()
302362
if (isChatbotCheckout) {
@@ -391,7 +451,6 @@ export default function PlansAndPayment ({ userData }) {
391451

392452
const defaultResource = resources["BD Gratis"]
393453
const planResource = resources[userData?.proSubscription]
394-
const planCanceled = bdProSubscriptionInfo?.canceledAt
395454

396455
const controlResource = () => {
397456
if (!planActive || !planResource) return defaultResource
@@ -1428,29 +1487,12 @@ export default function PlansAndPayment ({ userData }) {
14281487
justifyContent="space-between"
14291488
>
14301489
<Stack spacing="8px" marginBottom={{ base: "16px", lg: "0" }}>
1431-
<Badge
1432-
width="fit-content"
1433-
padding="2px 4px"
1434-
textTransform="none"
1435-
borderRadius="6px"
1436-
backgroundColor={
1437-
planActive ? (planCanceled ? "#F6E3E3" : "#D5E8DB") : "#D5E8DB"
1438-
}
1439-
color={
1440-
planActive ? (planCanceled ? "#BF3434" : "#2B8C4D") : "#2B8C4D"
1441-
}
1442-
fontSize="12px"
1443-
lineHeight="18px"
1444-
fontFamily="Roboto"
1445-
fontWeight="500"
1446-
letterSpacing="0.1px"
1447-
>
1448-
{planActive
1449-
? planCanceled
1450-
? t("username.canceled")
1451-
: t("username.active")
1452-
: t("username.active")}
1453-
</Badge>
1490+
{planActive && (
1491+
<SubscriptionStatusBadge
1492+
subscription={bdProSubscriptionInfo}
1493+
t={t}
1494+
/>
1495+
)}
14541496

14551497
<Box
14561498
display="flex"
@@ -1614,27 +1656,10 @@ export default function PlansAndPayment ({ userData }) {
16141656
marginTop="8px"
16151657
>
16161658
{hasChatbotActiveSubscription && (
1617-
<Badge
1618-
width="fit-content"
1619-
padding="2px 4px"
1620-
textTransform="none"
1621-
borderRadius="6px"
1622-
backgroundColor={
1623-
chatbotSubscriptionInfo?.canceledAt ? "#F6E3E3" : "#D5E8DB"
1624-
}
1625-
color={
1626-
chatbotSubscriptionInfo?.canceledAt ? "#BF3434" : "#2B8C4D"
1627-
}
1628-
fontSize="12px"
1629-
lineHeight="18px"
1630-
fontFamily="Roboto"
1631-
fontWeight="500"
1632-
letterSpacing="0.1px"
1633-
>
1634-
{chatbotSubscriptionInfo?.canceledAt
1635-
? t("username.canceled")
1636-
: t("username.active")}
1637-
</Badge>
1659+
<SubscriptionStatusBadge
1660+
subscription={chatbotSubscriptionInfo}
1661+
t={t}
1662+
/>
16381663
)}
16391664
<Box
16401665
display="flex"
@@ -1661,7 +1686,7 @@ export default function PlansAndPayment ({ userData }) {
16611686
>
16621687
{t("username.chatbotNewBadge")}
16631688
</Badge>
1664-
{chatbotSubscriptionInfo?.planInterval && (
1689+
{chatbotSubscriptionInfo?.planInterval && !isChatbotTrial && (
16651690
<LabelText typography="x-small" color="#71757A">
16661691
{formattedPlanInterval(
16671692
chatbotSubscriptionInfo?.planInterval,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import axios from "axios";
2+
3+
const API_URL= `${process.env.NEXT_PUBLIC_API_URL}/api/v1/graphql`
4+
5+
async function startChatbotTrial({id, token}) {
6+
const query = `
7+
mutation {
8+
startChatbotTrial (priceId: ${id}) {
9+
chatbotTrialEndsAt
10+
errors
11+
}
12+
}
13+
`
14+
15+
try {
16+
const res = await axios({
17+
url: API_URL,
18+
method: "POST",
19+
headers: {
20+
Authorization: `Bearer ${token}`
21+
},
22+
data: {
23+
query: query
24+
}
25+
})
26+
const data = res.data
27+
return data
28+
} catch (error) {
29+
console.error(error)
30+
return "err"
31+
}
32+
}
33+
34+
export default async function handler(req, res) {
35+
const token = req.cookies.token
36+
const result = await startChatbotTrial({
37+
id: atob(req.query.p),
38+
token: token
39+
})
40+
41+
if(result === "err") return res.status(200).json({started: false})
42+
if(result.errors) return res.status(200).json({started: false})
43+
44+
const trial = result?.data?.startChatbotTrial
45+
if(!trial || (trial.errors && trial.errors.length > 0)) return res.status(200).json({started: false})
46+
47+
res.status(200).json({started: true, chatbotTrialEndsAt: trial.chatbotTrialEndsAt})
48+
}

next/pages/api/user/getAlreadySubscribed.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,28 @@ export default async function handler(req, res) {
6767
}
6868

6969
const accountNode = result?.data?.allAccount?.edges[0]?.node;
70+
const subscriptionType = req.query.type;
71+
const internalSubs = accountNode?.internalSubscription?.edges || [];
7072

71-
const hasSubscriptionSet = accountNode?.subscriptionSet?.edges?.length > 0;
72-
const hasInternalSubscription = accountNode?.internalSubscription?.edges?.length > 0;
73-
74-
const isSubscriber = hasSubscriptionSet || hasInternalSubscription;
73+
const hadBDProSubscription =
74+
(accountNode?.subscriptionSet?.edges?.length > 0) ||
75+
internalSubs.some((edge) => {
76+
const slug = (edge?.node?.stripeSubscription || "").toLowerCase();
77+
return slug.includes("bd_pro") || slug.includes("empresas");
78+
});
79+
80+
const hadChatbotSubscription = internalSubs.some((edge) =>
81+
(edge?.node?.stripeSubscription || "").toLowerCase().includes("chatbot")
82+
);
83+
84+
let isSubscriber;
85+
if (subscriptionType === "bd_pro") {
86+
isSubscriber = hadBDProSubscription;
87+
} else if (subscriptionType === "chatbot") {
88+
isSubscriber = hadChatbotSubscription;
89+
} else {
90+
isSubscriber = hadBDProSubscription || hadChatbotSubscription;
91+
}
7592

7693
res.status(200).json(isSubscriber);
7794
} catch (error) {

next/pages/api/user/getUser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ async function getUser(id, token) {
4242
canceledAt
4343
createdAt
4444
planInterval
45+
stripeSubscriptionStatus
4546
}
4647
}
4748
}
@@ -52,6 +53,7 @@ async function getUser(id, token) {
5253
createdAt
5354
isActive
5455
stripeSubscription
56+
stripeSubscriptionStatus
5557
planInterval
5658
nextBillingCycle
5759
}

next/pages/api/user/getUserTestCypress.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ async function getUserData(id, token) {
6767
canceledAt
6868
createdAt
6969
planInterval
70+
stripeSubscriptionStatus
7071
}
7172
}
7273
}
@@ -77,6 +78,7 @@ async function getUserData(id, token) {
7778
createdAt
7879
isActive
7980
stripeSubscription
81+
stripeSubscriptionStatus
8082
planInterval
8183
nextBillingCycle
8284
}

next/pages/chatbot-lp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ function ChatbotPricingCard() {
240240
if (match) {
241241
const [id] = match;
242242
promises.push(
243-
fetch(`/api/user/getAlreadySubscribed?p=${btoa(id)}`)
243+
fetch(`/api/user/getAlreadySubscribed?p=${btoa(id)}&type=chatbot`)
244244
.then((res) => res.json())
245245
.then(setHasSubscribed)
246246
.catch(() => setHasSubscribed(false))

0 commit comments

Comments
 (0)