Skip to content

Commit

Permalink
Merge pull request #123 from SocialGouv/feat/obiz-offer-variable
Browse files Browse the repository at this point in the history
feat: obiz offer variable
  • Loading branch information
ClementNumericite authored Nov 6, 2024
2 parents 0dbe01f + 954ceff commit b16d35c
Show file tree
Hide file tree
Showing 39 changed files with 31,157 additions and 70 deletions.
6 changes: 5 additions & 1 deletion webapp/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ WEB_PUSH_PRIVATE_KEY=web-push-private-key
# Widget
NEXT_PUBLIC_WIDGET_TOKEN_NAME=widget-token
WIDGET_SECRET_JWT=secret1
WIDGET_SECRET_DATA_ENCRYPTION=secret2
WIDGET_SECRET_DATA_ENCRYPTION=secret2

# Obiz API
OBIZ_PARTNER_ID=default
OBIZ_SECRET=default
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/public/images/dashboard/order-pdf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions webapp/src/components/cards/OrderCard.tsx
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;
243 changes: 243 additions & 0 deletions webapp/src/components/modals/ObizOrderProcessModal.tsx
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>
);
}
Loading

0 comments on commit b16d35c

Please sign in to comment.