From 20096797e0583a3de804419af404aa0ffa78b886 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Thu, 19 Sep 2024 14:49:08 +0100 Subject: [PATCH] Chore: Migrate Coupon fraud demo to the `app` router INTER-911 (#157) * chore: move client code to app router * chore: move server code to app router * chore: remove needless import * chore: fix build --- next.config.js | 4 ++ package.json | 2 +- src/Providers.tsx | 4 +- .../coupon-fraud/CouponFraud.tsx} | 11 ++--- .../coupon-fraud/api/claim/route.ts} | 42 ++++++++----------- .../coupon-fraud/couponFraud.module.scss | 0 src/app/coupon-fraud/embed/page.tsx | 9 ++++ src/app/coupon-fraud/page.tsx | 9 ++++ .../coupon-fraud/shoeAirMax.svg | 0 .../coupon-fraud/shoeAllStar.svg | 0 src/app/playground/Playground.tsx | 12 +----- src/app/playground/embed/page.tsx | 4 +- src/app/playground/page.tsx | 4 +- src/pages/_app.tsx | 10 ++--- src/pages/coupon-fraud/embed.tsx | 13 ------ yarn.lock | 8 ++-- 16 files changed, 62 insertions(+), 70 deletions(-) rename src/{pages/coupon-fraud/index.tsx => app/coupon-fraud/CouponFraud.tsx} (92%) rename src/{pages/api/coupon-fraud/claim.ts => app/coupon-fraud/api/claim/route.ts} (56%) rename src/{pages => app}/coupon-fraud/couponFraud.module.scss (100%) create mode 100644 src/app/coupon-fraud/embed/page.tsx create mode 100644 src/app/coupon-fraud/page.tsx rename src/{pages => app}/coupon-fraud/shoeAirMax.svg (100%) rename src/{pages => app}/coupon-fraud/shoeAllStar.svg (100%) delete mode 100644 src/pages/coupon-fraud/embed.tsx diff --git a/next.config.js b/next.config.js index 20af3f22..6eb56c0f 100644 --- a/next.config.js +++ b/next.config.js @@ -12,6 +12,10 @@ module.exports = { includePaths: [path.join(__dirname, 'src/styles')], prependData: `@import "common.scss";`, }, + experimental: { + // Necessary to prevent https://github.com/sequelize/sequelize/issues/16589 + serverComponentsExternalPackages: ['sequelize'], + }, async headers() { return [ { diff --git a/package.json b/package.json index cd4a58e0..b6aea694 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "react-syntax-highlighter": "^15.5.0", "react-use": "^17.5.0", "react18-json-view": "^0.2.9-canary.0", - "sequelize": "^6.37.1", + "sequelize": "^6.37.3", "sharp": "^0.33.2", "twilio": "^5.0.1" }, diff --git a/src/Providers.tsx b/src/Providers.tsx index dbcba834..680cfeda 100644 --- a/src/Providers.tsx +++ b/src/Providers.tsx @@ -4,6 +4,8 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import { SnackbarProvider } from 'notistack'; import { PropsWithChildren } from 'react'; import { CloseSnackbarButton, CustomSnackbar } from './client/components/common/Alert/Alert'; +import { FpjsProvider } from '@fingerprintjs/fingerprintjs-pro-react'; +import { FP_LOAD_OPTIONS } from './pages/_app'; const queryClient = new QueryClient({ defaultOptions: { @@ -32,7 +34,7 @@ function Providers({ children }: PropsWithChildren) { info: CustomSnackbar, }} > - {children} + {children} ); diff --git a/src/pages/coupon-fraud/index.tsx b/src/app/coupon-fraud/CouponFraud.tsx similarity index 92% rename from src/pages/coupon-fraud/index.tsx rename to src/app/coupon-fraud/CouponFraud.tsx index cc7a155f..43dfd495 100644 --- a/src/pages/coupon-fraud/index.tsx +++ b/src/app/coupon-fraud/CouponFraud.tsx @@ -1,8 +1,9 @@ +'use client'; + import { UseCaseWrapper } from '../../client/components/common/UseCaseWrapper/UseCaseWrapper'; import { useState } from 'react'; import React from 'react'; import { USE_CASES } from '../../client/components/common/content'; -import { CustomPageProps } from '../_app'; import styles from './couponFraud.module.scss'; import formStyles from '../../styles/forms.module.scss'; import classNames from 'classnames'; @@ -14,13 +15,13 @@ import { Cart } from '../../client/components/common/Cart/Cart'; import { useVisitorData } from '@fingerprintjs/fingerprintjs-pro-react'; import { TEST_IDS } from '../../client/testIDs'; import { useMutation } from 'react-query'; -import { CouponClaimPayload, CouponClaimResponse } from '../api/coupon-fraud/claim'; +import { CouponClaimPayload, CouponClaimResponse } from './api/claim/route'; const AIRMAX_PRICE = 356.02; const ALLSTAR_PRICE = 102.5; const TAXES = 6; -export default function CouponFraudUseCase({ embed }: CustomPageProps) { +export function CouponFraudUseCase() { const { getData: getVisitorData } = useVisitorData( { ignoreCache: true, @@ -36,7 +37,7 @@ export default function CouponFraudUseCase({ embed }: CustomPageProps) { mutationKey: ['request coupon claim'], mutationFn: async ({ couponCode }: Omit) => { const { requestId } = await getVisitorData({ ignoreCache: true }); - const response = await fetch('/api/coupon-fraud/claim', { + const response = await fetch('/coupon-fraud/api/claim', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -82,7 +83,7 @@ export default function CouponFraudUseCase({ embed }: CustomPageProps) { ]; return ( - +
diff --git a/src/pages/api/coupon-fraud/claim.ts b/src/app/coupon-fraud/api/claim/route.ts similarity index 56% rename from src/pages/api/coupon-fraud/claim.ts rename to src/app/coupon-fraud/api/claim/route.ts index dca1c8fd..424d4a94 100644 --- a/src/pages/api/coupon-fraud/claim.ts +++ b/src/app/coupon-fraud/api/claim/route.ts @@ -1,9 +1,8 @@ -import { isValidPostRequest } from '../../../server/server'; import { Op } from 'sequelize'; -import { COUPON_CODES, CouponClaimDbModel, CouponCodeString } from '../../../server/coupon-fraud/database'; -import { Severity, getAndValidateFingerprintResult } from '../../../server/checks'; -import { NextApiRequest, NextApiResponse } from 'next'; -import { COUPON_FRAUD_COPY } from '../../../server/coupon-fraud/copy'; +import { COUPON_CODES, CouponClaimDbModel, CouponCodeString } from '../../../../server/coupon-fraud/database'; +import { Severity, getAndValidateFingerprintResult } from '../../../../server/checks'; +import { COUPON_FRAUD_COPY } from '../../../../server/coupon-fraud/copy'; +import { NextResponse } from 'next/server'; export type CouponClaimPayload = { couponCode: string; @@ -19,34 +18,24 @@ const isCouponCode = (couponCode: string): couponCode is CouponCodeString => { return COUPON_CODES.includes(couponCode as CouponCodeString); }; -export default async function claimCouponHandler(req: NextApiRequest, res: NextApiResponse) { - // This API route accepts only POST requests. - const reqValidation = isValidPostRequest(req); - if (!reqValidation.okay) { - res.status(405).send({ severity: 'error', message: reqValidation.error }); - return; - } - - const { couponCode, requestId } = req.body as CouponClaimPayload; +export async function POST(req: Request): Promise> { + const { couponCode, requestId } = (await req.json()) as CouponClaimPayload; // Get the full Identification result from Fingerprint Server API and validate its authenticity const fingerprintResult = await getAndValidateFingerprintResult({ requestId, req }); if (!fingerprintResult.okay) { - res.status(403).send({ severity: 'error', message: fingerprintResult.error }); - return; + return NextResponse.json({ severity: 'error', message: fingerprintResult.error }, { status: 403 }); } // Get visitorId from the Server API Identification event const visitorId = fingerprintResult.data.products?.identification?.data?.visitorId; if (!visitorId) { - res.status(403).send({ severity: 'error', message: 'Visitor ID not found.' }); - return; + return NextResponse.json({ severity: 'error', message: 'Visitor ID not found.' }, { status: 403 }); } // Check if Coupon exists if (!isCouponCode(couponCode)) { - res.status(403).send({ severity: 'error', message: COUPON_FRAUD_COPY.doesNotExist }); - return; + return NextResponse.json({ severity: 'error', message: COUPON_FRAUD_COPY.doesNotExist }, { status: 403 }); } // Check if visitor used this coupon before @@ -54,8 +43,7 @@ export default async function claimCouponHandler(req: NextApiRequest, res: NextA where: { visitorId, couponCode }, }); if (usedBefore) { - res.status(403).send({ severity: 'error', message: COUPON_FRAUD_COPY.usedBefore }); - return; + return NextResponse.json({ severity: 'error', message: COUPON_FRAUD_COPY.usedBefore }, { status: 403 }); } // Check if visitor claimed another coupon recently @@ -70,8 +58,12 @@ export default async function claimCouponHandler(req: NextApiRequest, res: NextA }, }); if (usedAnotherCouponRecently) { - res.status(403).send({ severity: 'error', message: COUPON_FRAUD_COPY.usedAnotherCouponRecently }); - return; + return NextResponse.json( + { severity: 'error', message: COUPON_FRAUD_COPY.usedAnotherCouponRecently }, + { + status: 403, + }, + ); } // If all checks passed, claim coupon @@ -80,5 +72,5 @@ export default async function claimCouponHandler(req: NextApiRequest, res: NextA visitorId, timestamp: new Date(), }); - res.status(200).send({ severity: 'success', message: COUPON_FRAUD_COPY.success }); + return NextResponse.json({ severity: 'success', message: COUPON_FRAUD_COPY.success }); } diff --git a/src/pages/coupon-fraud/couponFraud.module.scss b/src/app/coupon-fraud/couponFraud.module.scss similarity index 100% rename from src/pages/coupon-fraud/couponFraud.module.scss rename to src/app/coupon-fraud/couponFraud.module.scss diff --git a/src/app/coupon-fraud/embed/page.tsx b/src/app/coupon-fraud/embed/page.tsx new file mode 100644 index 00000000..c723d5bb --- /dev/null +++ b/src/app/coupon-fraud/embed/page.tsx @@ -0,0 +1,9 @@ +import { USE_CASES } from '../../../client/components/common/content'; +import { generateUseCaseMetadata } from '../../../client/components/common/seo'; +import { CouponFraudUseCase } from '../CouponFraud'; + +export const metadata = generateUseCaseMetadata(USE_CASES.couponFraud); + +export default function CouponFraudPage() { + return ; +} diff --git a/src/app/coupon-fraud/page.tsx b/src/app/coupon-fraud/page.tsx new file mode 100644 index 00000000..6f4f8ebe --- /dev/null +++ b/src/app/coupon-fraud/page.tsx @@ -0,0 +1,9 @@ +import { USE_CASES } from '../../client/components/common/content'; +import { generateUseCaseMetadata } from '../../client/components/common/seo'; +import { CouponFraudUseCase } from './CouponFraud'; + +export const metadata = generateUseCaseMetadata(USE_CASES.couponFraud); + +export default function CoupounFraudPage() { + return ; +} diff --git a/src/pages/coupon-fraud/shoeAirMax.svg b/src/app/coupon-fraud/shoeAirMax.svg similarity index 100% rename from src/pages/coupon-fraud/shoeAirMax.svg rename to src/app/coupon-fraud/shoeAirMax.svg diff --git a/src/pages/coupon-fraud/shoeAllStar.svg b/src/app/coupon-fraud/shoeAllStar.svg similarity index 100% rename from src/pages/coupon-fraud/shoeAllStar.svg rename to src/app/coupon-fraud/shoeAllStar.svg diff --git a/src/app/playground/Playground.tsx b/src/app/playground/Playground.tsx index 1aabd753..d3b4b6c2 100644 --- a/src/app/playground/Playground.tsx +++ b/src/app/playground/Playground.tsx @@ -10,13 +10,11 @@ import { ipBlocklistResult } from './components/IpBlocklistResult'; import { vpnDetectionResult } from './components/VpnDetectionResult'; import { usePlaygroundSignals } from './hooks/usePlaygroundSignals'; import { getLocationName, getZoomLevel } from '../../shared/utils/locationUtils'; -import { FP_LOAD_OPTIONS } from '../../pages/_app'; import Link from 'next/link'; import styles from './playground.module.scss'; import { Spinner } from '../../client/components/common/Spinner/Spinner'; import { Alert } from '../../client/components/common/Alert/Alert'; import { timeAgoLabel } from '../../shared/timeUtils'; -import { FpjsProvider } from '@fingerprintjs/fingerprintjs-pro-react'; import Container from '../../client/components/common/Container'; import { TEST_IDS } from '../../client/testIDs'; import tableStyles from './components/SignalTable.module.scss'; @@ -82,7 +80,7 @@ const TableTitle = ({ children }: { children: ReactNode }) => ( ); -function Playground() { +export function Playground() { const { agentResponse, isLoadingAgentResponse, @@ -700,11 +698,3 @@ function Playground() { ); } - -export default function PlaygroundPage() { - return ( - - - - ); -} diff --git a/src/app/playground/embed/page.tsx b/src/app/playground/embed/page.tsx index 5acb61ff..74a4795c 100644 --- a/src/app/playground/embed/page.tsx +++ b/src/app/playground/embed/page.tsx @@ -1,9 +1,9 @@ import { PLAYGROUND_METADATA } from '../../../client/components/common/content'; import { generateUseCaseMetadata } from '../../../client/components/common/seo'; -import PlaygroundPage from '../Playground'; +import { Playground } from '../Playground'; export const metadata = generateUseCaseMetadata(PLAYGROUND_METADATA); export default function VpnDetectionPage() { - return ; + return ; } diff --git a/src/app/playground/page.tsx b/src/app/playground/page.tsx index fdaa8c96..75ac4b96 100644 --- a/src/app/playground/page.tsx +++ b/src/app/playground/page.tsx @@ -1,9 +1,9 @@ import { PLAYGROUND_METADATA } from '../../client/components/common/content'; import { generateUseCaseMetadata } from '../../client/components/common/seo'; -import PlaygroundPage from './Playground'; +import { Playground } from './Playground'; export const metadata = generateUseCaseMetadata(PLAYGROUND_METADATA); export default function VpnDetectionPage() { - return ; + return ; } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 3ba745ef..b99a2f8d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,6 +1,6 @@ import '../styles/global-styles.scss'; import Head from 'next/head'; -import { FpjsProvider, FingerprintJSPro } from '@fingerprintjs/fingerprintjs-pro-react'; +import { FingerprintJSPro } from '@fingerprintjs/fingerprintjs-pro-react'; import { AppProps } from 'next/app'; import Providers from '../Providers'; import { Layout } from '../Layout'; @@ -24,11 +24,9 @@ function CustomApp({ Component, pageProps }: AppProps) { Fingerprint Pro Use Cases - - - - - + + + ); diff --git a/src/pages/coupon-fraud/embed.tsx b/src/pages/coupon-fraud/embed.tsx deleted file mode 100644 index 0e5ea28f..00000000 --- a/src/pages/coupon-fraud/embed.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import CouponFraud from '.'; -import { GetStaticProps } from 'next'; -import { CustomPageProps } from '../_app'; - -export default CouponFraud; - -export const getStaticProps: GetStaticProps = async () => { - return { - props: { - embed: true, - }, - }; -}; diff --git a/yarn.lock b/yarn.lock index e0d710c5..c5d5a73d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5485,10 +5485,10 @@ sequelize-pool@^7.1.0: resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-7.1.0.tgz#210b391af4002762f823188fd6ecfc7413020768" integrity sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg== -sequelize@^6.37.1: - version "6.37.1" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.37.1.tgz#9380fe0a3b5ff17638d3fce30c3cf3a2396c2343" - integrity sha512-vIKKzQ9dGp2aBOxQRD1FmUYViuQiKXSJ8yah8TsaBx4U3BokJt+Y2A0qz2C4pj08uX59qpWxRqSLEfRmVOEgQw== +sequelize@^6.37.3: + version "6.37.3" + resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.37.3.tgz#ed6212029a52c59a18638d2a703da84bc2f81311" + integrity sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A== dependencies: "@types/debug" "^4.1.8" "@types/validator" "^13.7.17"