payment
add payment
This commit is contained in:
parent
19039ead3a
commit
21bedbcc53
@ -11,11 +11,13 @@ export async function GET(req: NextRequest) {
|
|||||||
const productPrice = req.nextUrl.searchParams.get("price");
|
const productPrice = req.nextUrl.searchParams.get("price");
|
||||||
const currency = req.nextUrl.searchParams.get("currency");
|
const currency = req.nextUrl.searchParams.get("currency");
|
||||||
|
|
||||||
|
console.log("PRODUCT ID:", productId);
|
||||||
const data = await createPaymentCheckout({
|
const data = await createPaymentCheckout({
|
||||||
productId: productId || "",
|
productId: productId || "",
|
||||||
placementId: placementId || "",
|
placementId: placementId || "",
|
||||||
paywallId: paywallId || "",
|
paywallId: paywallId || "",
|
||||||
});
|
});
|
||||||
|
console.log("DATA:", data);
|
||||||
|
|
||||||
let redirectUrl: URL = new URL(data?.paymentUrl || "", req.nextUrl.origin);
|
let redirectUrl: URL = new URL(data?.paymentUrl || "", req.nextUrl.origin);
|
||||||
if (!redirectUrl) {
|
if (!redirectUrl) {
|
||||||
|
|||||||
@ -31,7 +31,11 @@ function estimatePathLength(
|
|||||||
const currentScreen = funnel.screens.find((s) => s.id === currentScreenId);
|
const currentScreen = funnel.screens.find((s) => s.id === currentScreenId);
|
||||||
if (!currentScreen) break;
|
if (!currentScreen) break;
|
||||||
|
|
||||||
const resolvedScreen = resolveScreenVariant(currentScreen, answers, funnel.screens);
|
const resolvedScreen = resolveScreenVariant(
|
||||||
|
currentScreen,
|
||||||
|
answers,
|
||||||
|
funnel.screens
|
||||||
|
);
|
||||||
const nextScreenId = resolveNextScreenId(
|
const nextScreenId = resolveNextScreenId(
|
||||||
resolvedScreen,
|
resolvedScreen,
|
||||||
answers,
|
answers,
|
||||||
@ -86,13 +90,6 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
|||||||
createSession();
|
createSession();
|
||||||
}, [createSession]);
|
}, [createSession]);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// // updateSession({
|
|
||||||
// // answers: answers,
|
|
||||||
// // });
|
|
||||||
// console.log("answers", answers);
|
|
||||||
// }, [answers]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerScreen(currentScreen.id);
|
registerScreen(currentScreen.id);
|
||||||
}, [currentScreen.id, registerScreen]);
|
}, [currentScreen.id, registerScreen]);
|
||||||
@ -130,10 +127,7 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleContinue = () => {
|
const handleContinue = () => {
|
||||||
console.log({
|
if (answers[currentScreen.id] && currentScreen.template !== "email") {
|
||||||
[currentScreen.id]: answers[currentScreen.id],
|
|
||||||
});
|
|
||||||
if (answers[currentScreen.id]) {
|
|
||||||
updateSession({
|
updateSession({
|
||||||
answers: {
|
answers: {
|
||||||
[currentScreen.id]: answers[currentScreen.id],
|
[currentScreen.id]: answers[currentScreen.id],
|
||||||
@ -223,9 +217,6 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
|||||||
|
|
||||||
// Auto-advance for single selection without action button
|
// Auto-advance for single selection without action button
|
||||||
if (shouldAutoAdvance) {
|
if (shouldAutoAdvance) {
|
||||||
console.log({
|
|
||||||
[currentScreen.id]: ids,
|
|
||||||
});
|
|
||||||
updateSession({
|
updateSession({
|
||||||
answers: {
|
answers: {
|
||||||
[currentScreen.id]: ids,
|
[currentScreen.id]: ids,
|
||||||
|
|||||||
@ -78,8 +78,10 @@ export function EmailTemplate({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authorization(email);
|
const token = await authorization(email);
|
||||||
onContinue();
|
if (token) {
|
||||||
|
onContinue();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Authorization failed:", err);
|
console.error("Authorization failed:", err);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,15 +33,16 @@ import {
|
|||||||
} from "@/components/domains/TrialPayment/Cards";
|
} from "@/components/domains/TrialPayment/Cards";
|
||||||
import ProgressToSeeSoulmate from "@/components/domains/TrialPayment/ProgressToSeeSoulmate/ProgressToSeeSoulmate";
|
import ProgressToSeeSoulmate from "@/components/domains/TrialPayment/ProgressToSeeSoulmate/ProgressToSeeSoulmate";
|
||||||
import { buildTypographyProps } from "@/lib/funnel/mappers";
|
import { buildTypographyProps } from "@/lib/funnel/mappers";
|
||||||
// import { usePaymentPlacement } from "@/hooks/payment/usePaymentPlacement";
|
import { usePaymentPlacement } from "@/hooks/payment/usePaymentPlacement";
|
||||||
// import { useRouter } from "next/navigation";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
// import { ROUTES } from "@/shared/constants/client-routes";
|
import { Currency } from "@/shared/types";
|
||||||
|
import { getFormattedPrice } from "@/shared/utils/price";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
|
|
||||||
interface TrialPaymentTemplateProps {
|
interface TrialPaymentTemplateProps {
|
||||||
funnel: FunnelDefinition;
|
funnel: FunnelDefinition;
|
||||||
screen: TrialPaymentScreenDefinition;
|
screen: TrialPaymentScreenDefinition;
|
||||||
// selectedEmail: string;
|
|
||||||
// onEmailChange: (email: string) => void;
|
|
||||||
onContinue: () => void;
|
onContinue: () => void;
|
||||||
canGoBack: boolean;
|
canGoBack: boolean;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
@ -50,36 +51,44 @@ interface TrialPaymentTemplateProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TrialPaymentTemplate({
|
export function TrialPaymentTemplate({
|
||||||
// funnel,
|
funnel,
|
||||||
screen,
|
screen,
|
||||||
// selectedEmail,
|
|
||||||
// onEmailChange,
|
|
||||||
// onContinue,
|
|
||||||
canGoBack,
|
canGoBack,
|
||||||
onBack,
|
onBack,
|
||||||
screenProgress,
|
screenProgress,
|
||||||
defaultTexts,
|
defaultTexts,
|
||||||
}: TrialPaymentTemplateProps) {
|
}: TrialPaymentTemplateProps) {
|
||||||
// const router = useRouter();
|
const router = useRouter();
|
||||||
// TODO: выбрать корректный paymentId для этого экрана (ключ из backend), временно "main_secret_discount"
|
|
||||||
// const paymentId = "main_secret_discount";
|
// TODO: выбрать корректный paymentId для этого экрана (ключ из backend), временно "main"
|
||||||
// const { placement } = usePaymentPlacement({ funnel, paymentId });
|
const paymentId = "main";
|
||||||
|
const { placement, isLoading } = usePaymentPlacement({ funnel, paymentId });
|
||||||
|
|
||||||
|
const trialInterval = placement?.trialInterval || 7;
|
||||||
|
const trialPeriod = placement?.trialPeriod;
|
||||||
|
const variant = placement?.variants?.[0];
|
||||||
|
const productId = variant?.id || "";
|
||||||
|
const placementId = placement?.placementId || "";
|
||||||
|
const paywallId = placement?.paywallId || "";
|
||||||
|
const trialPrice = variant?.trialPrice || 0;
|
||||||
|
const price = variant?.price || 0;
|
||||||
|
const oldPrice = variant?.price || 0;
|
||||||
|
const billingPeriod = placement?.billingPeriod;
|
||||||
|
const billingInterval = placement?.billingInterval || 1;
|
||||||
|
const currency = placement?.currency || Currency.USD;
|
||||||
|
|
||||||
|
console.log({ placement });
|
||||||
|
|
||||||
const handlePayClick = () => {
|
const handlePayClick = () => {
|
||||||
// const productId =
|
router.push(
|
||||||
// placement?.variants?.[0]?.id || placement?.variants?.[0]?.key || "";
|
ROUTES.payment({
|
||||||
// const placementId = placement?.placementId || "";
|
productId,
|
||||||
// const paywallId = placement?.paywallId || "";
|
placementId,
|
||||||
// if (productId && placementId && paywallId) {
|
paywallId,
|
||||||
// router.push(
|
})
|
||||||
// ROUTES.payment({
|
);
|
||||||
// productId,
|
|
||||||
// placementId,
|
|
||||||
// paywallId,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const paymentSectionRef = useRef<HTMLDivElement | null>(null);
|
const paymentSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const scrollToPayment = () => {
|
const scrollToPayment = () => {
|
||||||
@ -91,6 +100,86 @@ export function TrialPaymentTemplate({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatPeriod = (
|
||||||
|
period: "DAY" | "WEEK" | "MONTH" | "YEAR" | undefined,
|
||||||
|
interval: number
|
||||||
|
) => {
|
||||||
|
if (!period) return `${interval} days`;
|
||||||
|
const unit =
|
||||||
|
period === "DAY"
|
||||||
|
? interval === 1
|
||||||
|
? "day"
|
||||||
|
: "days"
|
||||||
|
: period === "WEEK"
|
||||||
|
? interval === 1
|
||||||
|
? "week"
|
||||||
|
: "weeks"
|
||||||
|
: period === "MONTH"
|
||||||
|
? interval === 1
|
||||||
|
? "month"
|
||||||
|
: "months"
|
||||||
|
: interval === 1
|
||||||
|
? "year"
|
||||||
|
: "years";
|
||||||
|
return `${interval} ${unit}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedTrialPrice = getFormattedPrice(trialPrice, currency);
|
||||||
|
const formattedBillingPrice = getFormattedPrice(price, currency);
|
||||||
|
const trialPeriodText = formatPeriod(trialPeriod, trialInterval);
|
||||||
|
const billingPeriodText = formatPeriod(billingPeriod, billingInterval);
|
||||||
|
|
||||||
|
const computeDiscountPercent = () => {
|
||||||
|
if (!oldPrice || !trialPrice || oldPrice <= 0) return undefined;
|
||||||
|
const ratio = 1 - trialPrice / oldPrice;
|
||||||
|
const percent = Math.max(0, Math.min(100, Math.round(ratio * 100)));
|
||||||
|
return String(percent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPeriodHyphen = (
|
||||||
|
period: "DAY" | "WEEK" | "MONTH" | "YEAR" | undefined,
|
||||||
|
interval: number
|
||||||
|
) => {
|
||||||
|
if (!period) return `${interval}-day`;
|
||||||
|
const unit =
|
||||||
|
period === "DAY"
|
||||||
|
? interval === 1
|
||||||
|
? "day"
|
||||||
|
: "days"
|
||||||
|
: period === "WEEK"
|
||||||
|
? interval === 1
|
||||||
|
? "week"
|
||||||
|
: "weeks"
|
||||||
|
: period === "MONTH"
|
||||||
|
? interval === 1
|
||||||
|
? "month"
|
||||||
|
: "months"
|
||||||
|
: interval === 1
|
||||||
|
? "year"
|
||||||
|
: "years";
|
||||||
|
return `${interval}-${unit}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const trialPeriodHyphenText = formatPeriodHyphen(trialPeriod, trialInterval);
|
||||||
|
|
||||||
|
const replacePlaceholders = (text: string | undefined) => {
|
||||||
|
if (!text) return "";
|
||||||
|
const values: Record<string, string> = {
|
||||||
|
trialPrice: formattedTrialPrice,
|
||||||
|
billingPrice: formattedBillingPrice,
|
||||||
|
oldPrice: getFormattedPrice(oldPrice || 0, currency),
|
||||||
|
discountPercent: computeDiscountPercent() ?? "",
|
||||||
|
trialPeriod: trialPeriodText,
|
||||||
|
billingPeriod: billingPeriodText,
|
||||||
|
trialPeriodHyphen: trialPeriodHyphenText,
|
||||||
|
};
|
||||||
|
let result = text;
|
||||||
|
for (const [key, value] of Object.entries(values)) {
|
||||||
|
result = result.replaceAll(`{{${key}}}`, value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
// Отключаем общий Header в TemplateLayout для этого экрана
|
// Отключаем общий Header в TemplateLayout для этого экрана
|
||||||
const screenWithoutHeader = {
|
const screenWithoutHeader = {
|
||||||
...screen,
|
...screen,
|
||||||
@ -126,6 +215,14 @@ export function TrialPaymentTemplate({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isLoading || !placement) {
|
||||||
|
return (
|
||||||
|
<div className="w-full min-h-dvh max-w-[560px] mx-auto flex items-center justify-center">
|
||||||
|
<Spinner className="size-8" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TemplateLayout
|
<TemplateLayout
|
||||||
{...layoutProps}
|
{...layoutProps}
|
||||||
@ -347,20 +444,26 @@ export function TrialPaymentTemplate({
|
|||||||
{screen.tryForDays && (
|
{screen.tryForDays && (
|
||||||
<TryForDays
|
<TryForDays
|
||||||
className="mt-[46px]"
|
className="mt-[46px]"
|
||||||
title={buildTypographyProps(screen.tryForDays.title, {
|
title={buildTypographyProps(
|
||||||
as: "h3",
|
{
|
||||||
defaults: { font: "inter", weight: "bold", size: "xl" },
|
...screen.tryForDays.title,
|
||||||
})}
|
text: replacePlaceholders(screen.tryForDays.title?.text),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
as: "h3",
|
||||||
|
defaults: { font: "inter", weight: "bold", size: "xl" },
|
||||||
|
}
|
||||||
|
)}
|
||||||
textListProps={
|
textListProps={
|
||||||
screen.tryForDays.textList
|
screen.tryForDays.textList
|
||||||
? {
|
? {
|
||||||
listStyleType: "none",
|
listStyleType: "none",
|
||||||
items: screen.tryForDays.textList.items.map(
|
items: screen.tryForDays.textList.items.map(
|
||||||
(it) =>
|
(it) =>
|
||||||
buildTypographyProps(it, {
|
buildTypographyProps(
|
||||||
as: "li",
|
{ ...it, text: replacePlaceholders(it.text) },
|
||||||
defaults: { font: "inter", size: "sm" },
|
{ as: "li", defaults: { font: "inter", size: "sm" } }
|
||||||
})!
|
)!
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
@ -397,21 +500,36 @@ export function TrialPaymentTemplate({
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
price: buildTypographyProps(
|
price: buildTypographyProps(
|
||||||
screen.totalPrice.priceContainer.price,
|
{
|
||||||
|
...screen.totalPrice.priceContainer.price,
|
||||||
|
text: replacePlaceholders(
|
||||||
|
screen.totalPrice.priceContainer.price?.text
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
as: "span",
|
as: "span",
|
||||||
defaults: { font: "inter", weight: "black" },
|
defaults: { font: "inter", weight: "black" },
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
oldPrice: buildTypographyProps(
|
oldPrice: buildTypographyProps(
|
||||||
screen.totalPrice.priceContainer.oldPrice,
|
{
|
||||||
|
...screen.totalPrice.priceContainer.oldPrice,
|
||||||
|
text: replacePlaceholders(
|
||||||
|
screen.totalPrice.priceContainer.oldPrice?.text
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
as: "span",
|
as: "span",
|
||||||
defaults: { font: "inter" },
|
defaults: { font: "inter" },
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
discount: buildTypographyProps(
|
discount: buildTypographyProps(
|
||||||
screen.totalPrice.priceContainer.discount,
|
{
|
||||||
|
...screen.totalPrice.priceContainer.discount,
|
||||||
|
text: replacePlaceholders(
|
||||||
|
screen.totalPrice.priceContainer.discount?.text
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
as: "span",
|
as: "span",
|
||||||
defaults: { font: "inter", weight: "bold", size: "sm" },
|
defaults: { font: "inter", weight: "bold", size: "sm" },
|
||||||
@ -530,10 +648,15 @@ export function TrialPaymentTemplate({
|
|||||||
{screen.policy && (
|
{screen.policy && (
|
||||||
<Policy
|
<Policy
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
text={buildTypographyProps(screen.policy.text, {
|
text={buildTypographyProps(
|
||||||
as: "p",
|
screen.policy.text
|
||||||
defaults: { font: "inter", size: "xs" },
|
? {
|
||||||
})}
|
...screen.policy.text,
|
||||||
|
text: replacePlaceholders(screen.policy.text.text),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
{ as: "p", defaults: { font: "inter", size: "xs" } }
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
import { http } from "@/shared/api/httpClient";
|
import { http } from "@/shared/api/httpClient";
|
||||||
import { API_ROUTES } from "@/shared/constants/api-routes";
|
import { API_ROUTES } from "@/shared/constants/api-routes";
|
||||||
|
|
||||||
|
|||||||
0
src/entities/session/funnel/serverActions.ts
Normal file
0
src/entities/session/funnel/serverActions.ts
Normal file
@ -39,7 +39,7 @@ export const CreateAuthorizeResponseSchema = z.object({
|
|||||||
token: z.string(),
|
token: z.string(),
|
||||||
userId: z.string().optional(),
|
userId: z.string().optional(),
|
||||||
generatingVideo: z.boolean().optional(),
|
generatingVideo: z.boolean().optional(),
|
||||||
videoId: z.string().optional(),
|
videoId: z.string().nullable().optional(),
|
||||||
authCode: z.string().optional(),
|
authCode: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -39,8 +39,8 @@ export const useAuth = ({ funnelId }: IUseAuthProps) => {
|
|||||||
timezone,
|
timezone,
|
||||||
locale,
|
locale,
|
||||||
email,
|
email,
|
||||||
// source: funnelId,
|
source: funnelId,
|
||||||
source: "aura.compatibility.v2",
|
// source: "aura.compatibility.v2",
|
||||||
// profile: {
|
// profile: {
|
||||||
// name: username || "",
|
// name: username || "",
|
||||||
// gender: EGender[gender as keyof typeof EGender] || null,
|
// gender: EGender[gender as keyof typeof EGender] || null,
|
||||||
@ -62,20 +62,7 @@ export const useAuth = ({ funnelId }: IUseAuthProps) => {
|
|||||||
// feature: feature.includes("black") ? "ios" : feature,
|
// feature: feature.includes("black") ? "ios" : feature,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[funnelId]
|
||||||
// birthPlace,
|
|
||||||
// birthdate,
|
|
||||||
// gender,
|
|
||||||
// locale,
|
|
||||||
// partnerBirthPlace,
|
|
||||||
// partnerBirthdate,
|
|
||||||
// partnerGender,
|
|
||||||
// partnerName,
|
|
||||||
// username,
|
|
||||||
// birthtime,
|
|
||||||
// checked,
|
|
||||||
// dateOfCheck,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const authorization = useCallback(
|
const authorization = useCallback(
|
||||||
@ -106,6 +93,7 @@ export const useAuth = ({ funnelId }: IUseAuthProps) => {
|
|||||||
// authCode,
|
// authCode,
|
||||||
} = await createAuthorization(payload);
|
} = await createAuthorization(payload);
|
||||||
await setAuthTokenToCookie(token);
|
await setAuthTokenToCookie(token);
|
||||||
|
return token;
|
||||||
// const { user: userMe } = await api.getMe({ token });
|
// const { user: userMe } = await api.getMe({ token });
|
||||||
// const userId = userIdFromApi || userMe?._id;
|
// const userId = userIdFromApi || userMe?._id;
|
||||||
// if (userId?.length) {
|
// if (userId?.length) {
|
||||||
|
|||||||
@ -51,7 +51,8 @@ export function usePaymentPlacement({
|
|||||||
setPlacement(normalized);
|
setPlacement(normalized);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
const message = e instanceof Error ? e.message : "Failed to load payment placement";
|
const message =
|
||||||
|
e instanceof Error ? e.message : "Failed to load payment placement";
|
||||||
setError(message);
|
setError(message);
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) setIsLoading(false);
|
if (isMounted) setIsLoading(false);
|
||||||
@ -65,5 +66,3 @@ export function usePaymentPlacement({
|
|||||||
|
|
||||||
return { placement, isLoading, error };
|
return { placement, isLoading, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -106,6 +106,7 @@ class HttpClient {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
|
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
|
||||||
headers.set("Content-Type", "application/json");
|
headers.set("Content-Type", "application/json");
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export function getClientAccessToken(): string | undefined {
|
|||||||
if (typeof window === "undefined") return undefined;
|
if (typeof window === "undefined") return undefined;
|
||||||
|
|
||||||
const cookies = document.cookie.split(";");
|
const cookies = document.cookie.split(";");
|
||||||
const accessTokenCookie = cookies.find(cookie =>
|
const accessTokenCookie = cookies.find((cookie) =>
|
||||||
cookie.trim().startsWith("accessToken=")
|
cookie.trim().startsWith("accessToken=")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
8
src/shared/constants/currency.ts
Normal file
8
src/shared/constants/currency.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Currency } from "@/shared/types";
|
||||||
|
|
||||||
|
export const symbolByCurrency: Record<string, string> = {
|
||||||
|
[Currency.USD]: "$",
|
||||||
|
[Currency.EUR]: "€",
|
||||||
|
[Currency.USD.toLowerCase()]: "$",
|
||||||
|
[Currency.EUR.toLowerCase()]: "€",
|
||||||
|
};
|
||||||
18
src/shared/utils/price.ts
Normal file
18
src/shared/utils/price.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Currency } from "@/shared/types";
|
||||||
|
import { symbolByCurrency } from "../constants/currency";
|
||||||
|
|
||||||
|
const addCurrency = (price: number | string, currency: Currency) => {
|
||||||
|
const symbol = symbolByCurrency[currency];
|
||||||
|
if ([Currency.EUR].includes(currency)) {
|
||||||
|
return `${price} ${symbol}`;
|
||||||
|
}
|
||||||
|
return `${symbol}${price}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFormattedPrice = (
|
||||||
|
price: number,
|
||||||
|
currency: Currency,
|
||||||
|
precision = 2
|
||||||
|
) => {
|
||||||
|
return addCurrency((price / 100).toFixed(precision), currency);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user