-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #123 from SocialGouv/feat/obiz-offer-variable
feat: obiz offer variable
- Loading branch information
Showing
39 changed files
with
31,157 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { Box, Flex, Tag, Text, Image as ChakraImage } from "@chakra-ui/react"; | ||
import { OrderIncluded } from "~/server/api/routers/order"; | ||
import Image from "../ui/Image"; | ||
import { BarcodeIcon } from "../icons/barcode"; | ||
import ConditionalLink from "../ConditionalLink"; | ||
import { useMemo } from "react"; | ||
import { formatter2Digits } from "~/utils/tools"; | ||
|
||
type OrderCardProps = { | ||
order: OrderIncluded; | ||
}; | ||
|
||
const OrderCard = (props: OrderCardProps) => { | ||
const { order } = props; | ||
|
||
const amount = useMemo(() => { | ||
return formatter2Digits.format( | ||
order.articles?.reduce((acc, curr) => { | ||
return acc + curr.article_montant * curr.article_quantity; | ||
}, 0) ?? 0 | ||
); | ||
}, [order.articles]); | ||
|
||
return ( | ||
<ConditionalLink | ||
to={`/dashboard/order/${order.id}`} | ||
condition | ||
props={{ | ||
_hover: { textDecoration: "none" }, | ||
}} | ||
> | ||
<Flex | ||
flexDir="column" | ||
px={4} | ||
pt={2} | ||
bg="white" | ||
borderRadius="2.5xl" | ||
shadow={"default-wallet"} | ||
h={"245px"} | ||
borderColor="cje-gray.400" | ||
overflow="hidden" | ||
gap={2} | ||
> | ||
{order.offer.partner.icon.url && ( | ||
<Flex alignItems={"center"} gap={2}> | ||
<Box | ||
flexGrow={0} | ||
rounded={"2xl"} | ||
borderWidth={1} | ||
borderColor={"bgGray"} | ||
overflow={"hidden"} | ||
> | ||
<Image | ||
src={order.offer.partner.icon.url} | ||
alt={order.offer.partner.icon.alt || ""} | ||
width={40} | ||
height={40} | ||
/> | ||
</Box> | ||
<Text fontWeight={700}>{order.offer.partner.name}</Text> | ||
</Flex> | ||
)} | ||
<Flex alignItems={"center"} gap={2}> | ||
<Tag fontWeight={700}> | ||
<BarcodeIcon mr={1} /> Bon d'achat | ||
</Tag> | ||
<Text fontWeight={700}>{amount} €</Text> | ||
</Flex> | ||
<Flex flexGrow={1}> | ||
<ChakraImage | ||
src={order.offer.image?.url || order.offer.partner.icon.url || ""} | ||
alt={`${order.offer.title} image`} | ||
mx="auto" | ||
h="full" | ||
/> | ||
</Flex> | ||
</Flex> | ||
</ConditionalLink> | ||
); | ||
}; | ||
|
||
export default OrderCard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
import { | ||
Box, | ||
Button, | ||
Icon, | ||
Modal, | ||
ModalBody, | ||
ModalContent, | ||
ModalOverlay, | ||
Text, | ||
} from "@chakra-ui/react"; | ||
import { Dispatch, SetStateAction, useState } from "react"; | ||
import DiscountAmountBlock from "~/components/obiz/DiscountAmountBlock"; | ||
import RecapOrder from "~/components/obiz/RecapOrder"; | ||
import BackButton from "~/components/ui/BackButton"; | ||
import { OfferIncluded } from "~/server/api/routers/offer"; | ||
import { OfferArticle } from "~/server/types"; | ||
import { api } from "~/utils/api"; | ||
import LayoutOrderStatus from "../obiz/LayoutOrderStatus"; | ||
import { HiMiniShieldCheck } from "react-icons/hi2"; | ||
|
||
const ObizOfferVariableContent = ({ | ||
step, | ||
setStep, | ||
amount, | ||
setAmount, | ||
offer, | ||
articles, | ||
createOrder, | ||
selectedArticles, | ||
setSelectedArticles, | ||
}: { | ||
step: Steps; | ||
setStep: Dispatch<SetStateAction<Steps>>; | ||
amount: number; | ||
setAmount: Dispatch<SetStateAction<number>>; | ||
articles: OfferArticle[]; | ||
selectedArticles: { article: OfferArticle; quantity: number }[]; | ||
setSelectedArticles: Dispatch< | ||
SetStateAction<{ article: OfferArticle; quantity: number }[]> | ||
>; | ||
offer: OfferIncluded; | ||
createOrder: () => void; | ||
}) => { | ||
const isVariablePrice = | ||
articles.length === 1 && articles[0].kind === "variable_price"; | ||
|
||
switch (step) { | ||
case "amount": | ||
if (isVariablePrice) { | ||
const article = articles[0]; | ||
const minimumPrice = article.minimumPrice ?? 0; | ||
const maximumPrice = article.maximumPrice ?? 1000; | ||
const isDisabled = | ||
amount === 0 || | ||
amount < minimumPrice || | ||
amount > maximumPrice || | ||
amount % 1 !== 0; | ||
|
||
return ( | ||
<> | ||
<Box mt={10}> | ||
<DiscountAmountBlock | ||
kind="variable_price" | ||
discount={article.reductionPercentage} | ||
amount={amount} | ||
setAmount={setAmount} | ||
minAmount={minimumPrice} | ||
maxAmount={maximumPrice} | ||
/> | ||
</Box> | ||
<Button | ||
mt="auto" | ||
mb={24} | ||
onClick={() => setStep("summary")} | ||
w="full" | ||
isDisabled={isDisabled} | ||
> | ||
Acheter mon bon | ||
</Button> | ||
</> | ||
); | ||
} else { | ||
if (!selectedArticles || !setSelectedArticles) return null; | ||
return ( | ||
<> | ||
<Box mt={10}> | ||
<DiscountAmountBlock | ||
kind="fixed_price" | ||
amount={amount} | ||
setAmount={setAmount} | ||
articles={articles} | ||
selectedArticles={selectedArticles} | ||
setSelectedArticles={setSelectedArticles} | ||
/> | ||
</Box> | ||
<Button | ||
mt="auto" | ||
mb={24} | ||
onClick={() => setStep("summary")} | ||
isDisabled={amount === 0} | ||
w="full" | ||
> | ||
Acheter ces bons | ||
</Button> | ||
</> | ||
); | ||
} | ||
case "summary": | ||
if (!offer) return null; | ||
return ( | ||
<> | ||
<Box mt={10}> | ||
{isVariablePrice ? ( | ||
<RecapOrder | ||
kind="variable_price" | ||
discount={articles[0].reductionPercentage} | ||
amount={amount} | ||
offer={offer} | ||
/> | ||
) : ( | ||
<RecapOrder | ||
kind="fixed_price" | ||
discount={articles[0].reductionPercentage} | ||
articles={selectedArticles} | ||
amount={amount} | ||
offer={offer} | ||
/> | ||
)} | ||
</Box> | ||
<Button mt={10} onClick={() => createOrder()} w="full"> | ||
Passer au paiement | ||
</Button> | ||
</> | ||
); | ||
case "payment": | ||
return ( | ||
<LayoutOrderStatus | ||
status="loading" | ||
title={`Vous allez payer ${amount}€`} | ||
footer={ | ||
<Box mt="auto" textAlign="center"> | ||
<Icon as={HiMiniShieldCheck} color="primary" boxSize={6} /> | ||
<Text fontSize={12} fontWeight={700} mt={4}> | ||
Tous les paiements et tous vos bons sont entièrement sécurisés | ||
</Text> | ||
<Text fontSize={12} fontWeight={700} mt={4}> | ||
L’application carte “jeune engagé” est un dispositif de l’État. | ||
</Text> | ||
</Box> | ||
} | ||
/> | ||
); | ||
} | ||
}; | ||
|
||
type ObizOrderProcessModalProps = { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
offerId: number; | ||
}; | ||
|
||
type Steps = "amount" | "summary" | "payment"; | ||
|
||
export default function ObizOrderProcessModal( | ||
props: ObizOrderProcessModalProps | ||
) { | ||
const { isOpen, onClose, offerId } = props; | ||
|
||
const [amount, setAmount] = useState(0); | ||
const [step, setStep] = useState<Steps>("amount"); | ||
const [selectedArticles, setSelectedArticles] = useState< | ||
{ article: OfferArticle; quantity: number }[] | ||
>([]); | ||
|
||
const { mutate: createTestOrder } = api.order.createOrder.useMutation({ | ||
onMutate: () => setStep("payment"), | ||
onSuccess: ({ data: { payment_url } }) => { | ||
if (payment_url) window.location.href = payment_url; | ||
}, | ||
}); | ||
|
||
const { data: offerResult } = api.offer.getById.useQuery({ | ||
id: offerId, | ||
}); | ||
const { data: offer } = offerResult || {}; | ||
|
||
if (!offer || !offer.articles) return; | ||
|
||
const availableArticles = offer.articles.filter((a) => !!a.available); | ||
|
||
if (availableArticles.length === 0) return; | ||
|
||
return ( | ||
<Modal isOpen={isOpen} onClose={onClose} size="full"> | ||
<ModalOverlay /> | ||
<ModalContent h="100dvh"> | ||
<ModalBody display="flex" flexDir="column" px={8} h="100dvh"> | ||
{step !== "payment" && ( | ||
<Box mt={8}> | ||
<BackButton | ||
onClick={() => | ||
step == "amount" ? onClose() : setStep("amount") | ||
} | ||
/> | ||
</Box> | ||
)} | ||
<ObizOfferVariableContent | ||
step={step} | ||
setStep={setStep} | ||
amount={amount} | ||
setAmount={setAmount} | ||
offer={offer} | ||
articles={availableArticles} | ||
createOrder={() => { | ||
if ( | ||
availableArticles.length === 1 && | ||
availableArticles[0].kind === "variable_price" | ||
) { | ||
createTestOrder({ | ||
offer_id: offer.id, | ||
article_references: [ | ||
{ reference: availableArticles[0].reference, quantity: 1 }, | ||
], | ||
input_value: amount, | ||
}); | ||
} else { | ||
createTestOrder({ | ||
offer_id: offer.id, | ||
article_references: selectedArticles.map((article) => ({ | ||
reference: article.article.reference, | ||
quantity: article.quantity, | ||
})), | ||
}); | ||
} | ||
}} | ||
selectedArticles={selectedArticles} | ||
setSelectedArticles={setSelectedArticles} | ||
/> | ||
</ModalBody> | ||
</ModalContent> | ||
</Modal> | ||
); | ||
} |
Oops, something went wrong.