From b345131b0b352e6f8abb96f52a796da3a55f4c83 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Mon, 14 Feb 2022 09:00:47 +0100 Subject: [PATCH] =?UTF-8?q?feat(account):=20=E2=9C=A8=20Add=20coupon=20cod?= =?UTF-8?q?e=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/account/BillingSection.tsx | 44 ++++++++++++++++++- apps/builder/pages/api/coupons/redeem.ts | 31 +++++++++++++ apps/builder/services/coupons.ts | 8 ++++ packages/db/package.json | 6 +-- packages/db/prisma/schema.prisma | 10 ++++- 5 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 apps/builder/pages/api/coupons/redeem.ts create mode 100644 apps/builder/services/coupons.ts diff --git a/apps/builder/components/account/BillingSection.tsx b/apps/builder/components/account/BillingSection.tsx index 44186a13e3..1d14bdea5e 100644 --- a/apps/builder/components/account/BillingSection.tsx +++ b/apps/builder/components/account/BillingSection.tsx @@ -1,12 +1,44 @@ -import { Stack, Heading, HStack, Button, Text } from '@chakra-ui/react' +import { + Stack, + Heading, + HStack, + Button, + Text, + Input, + useToast, +} from '@chakra-ui/react' import { NextChakraLink } from 'components/nextChakra/NextChakraLink' import { useUser } from 'contexts/UserContext' import { Plan } from 'db' -import React from 'react' +import { useRouter } from 'next/router' +import React, { useState } from 'react' +import { redeemCoupon } from 'services/coupons' import { SubscriptionTag } from './SubscriptionTag' export const BillingSection = () => { + const { reload } = useRouter() + const [isLoading, setIsLoading] = useState(false) + const { user } = useUser() + const toast = useToast({ + position: 'top-right', + }) + + const handleCouponCodeRedeem = async (e: React.FormEvent) => { + e.preventDefault() + const target = e.target as typeof e.target & { + coupon: { value: string } + } + setIsLoading(true) + const { data, error } = await redeemCoupon(target.coupon.value) + if (error) toast({ title: error.name, description: error.message }) + else { + toast({ description: data?.message }) + setTimeout(reload, 1000) + } + setIsLoading(false) + } + return ( @@ -25,6 +57,14 @@ export const BillingSection = () => { {user?.plan === Plan.FREE && ( )} + {user?.plan === Plan.FREE && ( + + + + + )} ) diff --git a/apps/builder/pages/api/coupons/redeem.ts b/apps/builder/pages/api/coupons/redeem.ts new file mode 100644 index 0000000000..8b3f90c9e7 --- /dev/null +++ b/apps/builder/pages/api/coupons/redeem.ts @@ -0,0 +1,31 @@ +import { Prisma, User } from 'db' +import prisma from 'libs/prisma' +import { NextApiRequest, NextApiResponse } from 'next' +import { getSession } from 'next-auth/react' + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === 'POST') { + const session = await getSession({ req }) + + if (!session?.user) + return res.status(401).json({ message: 'Not authenticated' }) + + const user = session.user as User + const { code } = JSON.parse(req.body) + const coupon = await prisma.coupon.findFirst({ + where: { code, dateRedeemed: null }, + }) + if (!coupon) return res.status(404).send({ message: 'Coupon not found' }) + await prisma.user.update({ + where: { id: user.id }, + data: coupon.userPropertiesToUpdate as Prisma.UserUncheckedUpdateInput, + }) + await prisma.coupon.update({ + where: { code }, + data: { dateRedeemed: new Date() }, + }) + return res.send({ message: 'Coupon redeemed 🎊' }) + } +} + +export default handler diff --git a/apps/builder/services/coupons.ts b/apps/builder/services/coupons.ts new file mode 100644 index 0000000000..4948caf8b3 --- /dev/null +++ b/apps/builder/services/coupons.ts @@ -0,0 +1,8 @@ +import { sendRequest } from 'utils' + +export const redeemCoupon = async (code: string) => + sendRequest<{ message: string }>({ + method: 'POST', + url: '/api/coupons/redeem', + body: { code }, + }) diff --git a/packages/db/package.json b/packages/db/package.json index 68998a46ed..fe37f2fffe 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -15,8 +15,8 @@ "scripts": { "dev": "dotenv -e .env yarn prisma db push && BROWSER=none yarn prisma studio", "build": "yarn migration:push", - "migration:push": "dotenv -e ../../.env yarn prisma db push", - "migration:create": "dotenv -e ../../.env yarn prisma migrate dev", - "migration:reset": "dotenv -e ../../.env yarn prisma migrate reset" + "migration:push": "dotenv -e .env yarn prisma db push", + "migration:create": "dotenv -e .env yarn prisma migrate dev", + "migration:reset": "dotenv -e .env yarn prisma migrate reset" } } diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 1c2044d910..5fdfbc9a34 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -53,9 +53,9 @@ model User { } model Credentials { - id String @id @default(cuid()) + id String @id @default(cuid()) ownerId String - owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) data String // Encrypted data name String type String @@ -143,3 +143,9 @@ model Answer { @@unique([resultId, blockId, stepId]) } + +model Coupon { + userPropertiesToUpdate Json + code String @id + dateRedeemed DateTime? +}