Skip to content

Commit 01eb8b8

Browse files
committed
feat: add referral links
1 parent e275c51 commit 01eb8b8

File tree

3 files changed

+107
-13
lines changed

3 files changed

+107
-13
lines changed

customer/hooks/useCart.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type CartItem = {
1515
}
1616

1717
export type CartContextType = {
18+
isLoading: boolean,
1819
cart: CartItem[],
1920
addItem: (item: CartItem) => void,
2021
updateItem: (item: CartItem) => void,
@@ -23,11 +24,12 @@ export type CartContextType = {
2324
}
2425

2526
const CartContext = createContext<CartContextType>({
27+
isLoading: true,
2628
cart: [],
2729
addItem: noop,
2830
clearCart: noop,
2931
removeItem: noop,
30-
updateItem: noop
32+
updateItem: noop,
3133
})
3234

3335

@@ -71,19 +73,20 @@ export const CartProvider = ({ children }: PropsWithChildren) => {
7173

7274
// Remove item from cart
7375
const removeItem = (id: string) => {
74-
setCart(cart.filter((item) => item.id !== id))
76+
setCart((prevState) => prevState.filter((item) => item.id !== id))
7577
}
7678

7779
// Update
7880
const updateItem = (update: CartItem) => {
79-
setCart(cart.map((item) => (item.id === update.id? update : item)))
81+
setCart(prevState => prevState.map((item) => (item.id === update.id? update : item)))
8082
}
8183

8284
// Clear cart
83-
const clearCart = () => setCart([])
85+
const clearCart = () => setCart(() => [])
8486

8587
return (
86-
<CartContext.Provider value={{ cart, addItem, removeItem, updateItem, clearCart }}>
88+
<CartContext.Provider
89+
value={{ cart, addItem, removeItem, updateItem, clearCart, isLoading }}>
8790
{children}
8891
</CartContext.Provider>
8992
)

customer/pages/products/overview.tsx

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { useProductsAllQuery } from '@/api/mutations/product_mutations'
77
import { Section } from '@/components/layout/Section'
88
import { tw, tx } from '@twind/core'
99
import type {
10-
ProductPlanTranslation } from '@/api/dataclasses/product'
10+
ProductPlanTranslation
11+
} from '@/api/dataclasses/product'
1112
import {
1213
defaultProductPlanTranslation
1314
} from '@/api/dataclasses/product'
@@ -21,12 +22,22 @@ import { withCart } from '@/hocs/withCart'
2122
import { LoadingAndErrorComponent } from '@helpwave/common/components/LoadingAndErrorComponent'
2223
import { useRouter } from 'next/router'
2324
import { Modal } from '@helpwave/common/components/modals/Modal'
24-
import { useState } from 'react'
25+
import { useEffect, useState } from 'react'
2526
import { Input } from '@helpwave/common/components/user-input/Input'
2627
import { VoucherAPI } from '@/api/services/voucher'
2728
import type { Voucher } from '@/api/dataclasses/voucher'
2829
import { Chip } from '@helpwave/common/components/ChipList'
2930
import { useCustomerProductsCalculateQuery } from '@/api/mutations/customer_product_mutations'
31+
import { ProductAPI } from '@/api/services/product'
32+
import { LoadingAnimation } from '@helpwave/common/components/LoadingAnimation'
33+
34+
type ReferralStatus = 'loading' | 'error'
35+
type ReferralData = {
36+
product: string,
37+
plan: string,
38+
voucher?: string,
39+
}
40+
3041

3142
type CartOverviewTranslation = {
3243
removeFromCart: string,
@@ -43,6 +54,8 @@ type CartOverviewTranslation = {
4354
redeemVoucherFor: (name: string) => string,
4455
code: string,
4556
invalidCode: string,
57+
referral: string,
58+
referralError: string,
4659
} & ProductPlanTranslation
4760

4861
const defaultCartOverviewTranslations: Record<Languages, CartOverviewTranslation> = {
@@ -62,6 +75,8 @@ const defaultCartOverviewTranslations: Record<Languages, CartOverviewTranslation
6275
redeemVoucherFor: (name: string) => `Redeem Voucher for ${name}`,
6376
code: 'Code',
6477
invalidCode: 'The provided Code is not valid (for this product), please try a different one.',
78+
referral: 'Referral',
79+
referralError: 'The Referral could not be processed.',
6580
},
6681
de: {
6782
...defaultProductPlanTranslation.de,
@@ -79,30 +94,103 @@ const defaultCartOverviewTranslations: Record<Languages, CartOverviewTranslation
7994
redeemVoucherFor: (name: string) => `Gutschein einlösen für ${name}`,
8095
code: 'Code',
8196
invalidCode: 'Der eingegebenen Gutschein-Code ist nicht gültig (für dieses Produkt), versuchen Sie einen anderen.',
97+
referral: 'Überweisung', // TODO fix translation
98+
referralError: 'Die Überweisung hat nicht funktioniert.',
8299
}
83100
}
84101

85102

86103
const CartOverview: NextPage = () => {
87104
const translation = useTranslation(defaultCartOverviewTranslations)
88105
const router = useRouter()
106+
const [hasUsedReferral, setHasUsedReferral] = useState<boolean>(false)
89107
const { authHeader } = useAuth()
90-
const { cart, removeItem, updateItem } = useCart()
108+
const [referralStatus, setReferralStatus] = useState<ReferralStatus>()
109+
const { cart, removeItem, updateItem, addItem, isLoading: cartIsLoading } = useCart()
91110
const [productVoucherModalId, setProductVoucherModalId] = useState<string>()
92111
const [voucherCode, setVoucherCode] = useState<string>('')
93112
const [redeemResponse, setRedeemResponse] = useState<string>()
94113
const { data: products, isError: productsError, isLoading: productsLoading } = useProductsAllQuery()
95-
const { data: prices, isError: pricesError, isLoading: pricesLoading } = useCustomerProductsCalculateQuery(cart.map(item => ({
114+
const {
115+
data: prices,
116+
isError: pricesError,
117+
isLoading: pricesLoading,
118+
isRefetching,
119+
refetch,
120+
} = useCustomerProductsCalculateQuery(cart.map(item => ({
96121
productUuid: item.id,
97122
productPlanUuid: item.plan.uuid,
98123
voucherUuid: item.voucher?.uuid
99124
})))
100125

126+
useEffect(() => {
127+
refetch().catch(console.error)
128+
}, [cart])
129+
130+
useEffect(() => {
131+
if (!router.isReady || cartIsLoading || hasUsedReferral) return
132+
133+
const referralParam = router.query['referral']
134+
135+
if (!referralParam || typeof referralParam !== 'string') return
136+
137+
try {
138+
const decoded = atob(referralParam)
139+
const parsed: ReferralData = JSON.parse(decoded)
140+
141+
const check = async () => {
142+
const products = await ProductAPI.getAvailable(authHeader)
143+
const product = products.find(value => value.uuid === parsed.product)
144+
const plan = product?.plan.find(value => value.uuid === parsed.plan)
145+
146+
// TODO try to parse voucher
147+
148+
if (!product || !plan) {
149+
setReferralStatus('error')
150+
setHasUsedReferral(true)
151+
return
152+
}
153+
154+
if (cart.find(value => value.id === product?.uuid)) {
155+
// TODO maybe show an additional dialog here
156+
updateItem({ id: product?.uuid, plan: plan, quantity: 1 })
157+
} else {
158+
addItem({ id: product?.uuid, plan: plan, quantity: 1 })
159+
}
160+
}
161+
162+
check().catch((reason) => {
163+
console.error(reason)
164+
setReferralStatus('error')
165+
setHasUsedReferral(true)
166+
}).then(() => {
167+
setReferralStatus(undefined)
168+
setHasUsedReferral(true)
169+
})
170+
} catch (err) {
171+
console.error(err)
172+
setReferralStatus('error')
173+
setHasUsedReferral(true)
174+
}
175+
}, [router, cart])
176+
101177
const isError = pricesError || productsError
102-
const isLoading = pricesLoading || productsLoading || Object.keys(prices?.products ?? {}).length === 0
178+
const isLoading = pricesLoading || productsLoading || isRefetching
103179

104180
return (
105181
<Page pageTitle={titleWrapper(translation.overview)}>
182+
<Modal
183+
id="referral-modal"
184+
isOpen={!!referralStatus}
185+
onBackgroundClick={referralStatus === 'error' ? () => setReferralStatus(undefined) : undefined}
186+
onCloseClick={referralStatus === 'error' ? () => setReferralStatus(undefined) : undefined}
187+
titleText={translation.referral}
188+
modalClassName={tw('gap-y-2')}
189+
>
190+
{referralStatus === 'error' ? (
191+
<span className={tw('text-hw-negative-500')}>{translation.referralError}</span>
192+
) : (<LoadingAnimation/>)}
193+
</Modal>
106194
<Modal
107195
id="voucher-modal"
108196
isOpen={!!productVoucherModalId}
@@ -153,7 +241,7 @@ const CartOverview: NextPage = () => {
153241
const plan = product?.plan.find(value => value.uuid === cartItem.plan.uuid)
154242
const voucher = cartItem.voucher
155243

156-
if(!product || !plan){
244+
if (!product || !plan) {
157245
return []
158246
}
159247
const priceResult = prices.products[product.uuid]!

customer/pages/products/pay.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type ProductsTranslation = {
3939
bookingFailure: string,
4040
bookingFailureDesc: string,
4141
toInvoices: string,
42+
noTermsAndConditions: string,
4243
} & ProductPlanTypeTranslation
4344

4445
const defaultProductsTranslations: Record<Languages, ProductsTranslation> = {
@@ -58,6 +59,7 @@ const defaultProductsTranslations: Record<Languages, ProductsTranslation> = {
5859
bookingFailure: 'Booking failed',
5960
bookingFailureDesc: 'Try again or contact our support at:',
6061
toInvoices: 'View Invoices',
62+
noTermsAndConditions: 'No Terms and Conditions required.',
6163
},
6264
de: {
6365
...defaultProductPlanTypeTranslation.de,
@@ -75,6 +77,7 @@ const defaultProductsTranslations: Record<Languages, ProductsTranslation> = {
7577
bookingFailure: 'Buchung fehlgeschlagen',
7678
bookingFailureDesc: 'Versuchen sie es erneut oder kontaktieren sie unseren Support unter:',
7779
toInvoices: 'Zu den Rechnungen',
80+
noTermsAndConditions: 'Keine Verträge und Nutzungsbedingungen benötigt',
7881
}
7982
}
8083

@@ -169,7 +172,7 @@ const Payment: NextPage = () => {
169172
<h4 className={tw('font-bold text-xl')}>{translation.termsAndConditions}</h4>
170173
{products && contracts && (
171174
<form className={tw('flex flex-col gap-y-4')}>
172-
{contracts.map((contract) => {
175+
{contracts.length > 0 ? contracts.map((contract) => {
173176
const isAccepted = acceptedContracts[contract.uuid] ?? false
174177
return (
175178
<div key={contract.uuid} className={tw('flex flex-row gap-x-2')}>
@@ -199,7 +202,7 @@ const Payment: NextPage = () => {
199202
</span>
200203
</div>
201204
)
202-
})}
205+
}) : <span className={tw('text-gray-500')}>{translation.noTermsAndConditions}</span>}
203206
<div className={tw('flex flex-row justify-between')}>
204207
<Button
205208
className={tw('flex flex-row items-center gap-x-2 w-[200px]')}

0 commit comments

Comments
 (0)