payment-edits
edits
This commit is contained in:
parent
3f8538f8b5
commit
caf517d1f0
@ -1,11 +0,0 @@
|
|||||||
export default function PaymentLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<main className="p-4 pb-220 max-w-[560px] mx-auto relative min-h-dvh">
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
// import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
import AnimatedInfoScreen from "@/components/widgets/AnimatedInfoScreen/AnimatedInfoScreen";
|
|
||||||
import LottieAnimation from "@/components/widgets/LottieAnimation/LottieAnimation";
|
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
|
||||||
import { ELottieKeys } from "@/shared/constants/lottie";
|
|
||||||
|
|
||||||
export default async function PaymentFailed() {
|
|
||||||
// const t = await getTranslations("Payment.Error");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatedInfoScreen
|
|
||||||
lottieAnimation={
|
|
||||||
<LottieAnimation loadKey={ELottieKeys.loaderCheckMark} />
|
|
||||||
}
|
|
||||||
// title={t("title")}
|
|
||||||
title="Payment failed"
|
|
||||||
animationTime={0}
|
|
||||||
animationTexts={[]}
|
|
||||||
buttonText="Try again"
|
|
||||||
nextRoute={ROUTES.home()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
|
|
||||||
import { createPaymentCheckout } from "@/entities/payment/api";
|
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
const productId = req.nextUrl.searchParams.get("productId");
|
|
||||||
const placementId = req.nextUrl.searchParams.get("placementId");
|
|
||||||
const paywallId = req.nextUrl.searchParams.get("paywallId");
|
|
||||||
const fbPixels = req.nextUrl.searchParams.get("fb_pixels");
|
|
||||||
const productPrice = req.nextUrl.searchParams.get("price");
|
|
||||||
const currency = req.nextUrl.searchParams.get("currency");
|
|
||||||
|
|
||||||
console.log("PRODUCT ID:", productId);
|
|
||||||
const data = await createPaymentCheckout({
|
|
||||||
productId: productId || "",
|
|
||||||
placementId: placementId || "",
|
|
||||||
paywallId: paywallId || "",
|
|
||||||
});
|
|
||||||
console.log("DATA:", data);
|
|
||||||
|
|
||||||
let redirectUrl: URL = new URL(data?.paymentUrl || "", req.nextUrl.origin);
|
|
||||||
if (!redirectUrl) {
|
|
||||||
redirectUrl = new URL(`${ROUTES.paymentFailed()}`, origin);
|
|
||||||
}
|
|
||||||
if (fbPixels) redirectUrl.searchParams.set("fb_pixels", fbPixels);
|
|
||||||
if (productPrice) redirectUrl.searchParams.set("price", productPrice);
|
|
||||||
if (currency) redirectUrl.searchParams.set("currency", currency);
|
|
||||||
|
|
||||||
return NextResponse.redirect(redirectUrl, { status: 307 });
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ActionButton } from "@/components/ui/ActionButton/ActionButton";
|
|
||||||
// import { useRouter } from "next/navigation";
|
|
||||||
// import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
import Typography from "@/components/ui/Typography/Typography";
|
|
||||||
// import { ROUTES } from "@/shared/constants/client-routes";
|
|
||||||
|
|
||||||
// import styles from "./Button.module.scss";
|
|
||||||
|
|
||||||
export default function PaymentSuccessButton() {
|
|
||||||
// const t = useTranslations("Payment.Success");
|
|
||||||
// const router = useRouter();
|
|
||||||
|
|
||||||
const handleNext = () => {
|
|
||||||
// router.push(ROUTES.additionalPurchases());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ActionButton
|
|
||||||
onClick={handleNext}
|
|
||||||
className="fixed bottom-[calc(0dvh+64px)] left-1/2 -translate-x-1/2 opacity-0 pointer-events-none max-w-[400px] w-[calc(100dvw-32px)] [animation:fadeIn_0.5s_ease-in-out_forwards] [animation-delay:2s]"
|
|
||||||
>
|
|
||||||
<Typography color="primary" size="xl" weight="bold">
|
|
||||||
{/* {t("button")} */}
|
|
||||||
Done
|
|
||||||
</Typography>
|
|
||||||
</ActionButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
// import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
import AnimatedInfoScreen from "@/components/widgets/AnimatedInfoScreen/AnimatedInfoScreen";
|
|
||||||
import LottieAnimation from "@/components/widgets/LottieAnimation/LottieAnimation";
|
|
||||||
|
|
||||||
import PaymentSuccessButton from "./Button";
|
|
||||||
import { ELottieKeys } from "@/shared/constants/lottie";
|
|
||||||
|
|
||||||
export default async function PaymentSuccess() {
|
|
||||||
// const t = await getTranslations("Payment.Success");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<AnimatedInfoScreen
|
|
||||||
lottieAnimation={
|
|
||||||
<LottieAnimation loadKey={ELottieKeys.loaderCheckMark} />
|
|
||||||
}
|
|
||||||
// title={t("title")}
|
|
||||||
title="Payment successful"
|
|
||||||
/>
|
|
||||||
<PaymentSuccessButton />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -37,8 +37,39 @@ import { usePaymentPlacement } from "@/hooks/payment/usePaymentPlacement";
|
|||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Currency } from "@/shared/types";
|
import { Currency } from "@/shared/types";
|
||||||
import { getFormattedPrice } from "@/shared/utils/price";
|
import { getFormattedPrice } from "@/shared/utils/price";
|
||||||
import { useRouter } from "next/navigation";
|
import { useClientToken } from "@/hooks/auth/useClientToken";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
|
||||||
|
function getTrackingCookiesForRedirect() {
|
||||||
|
const cookieObj = Object.fromEntries(
|
||||||
|
document.cookie.split("; ").map((c) => c.split("="))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = Object.entries(cookieObj).filter(([key]) => {
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
"_fbc",
|
||||||
|
"_fbp",
|
||||||
|
"_ym_uid",
|
||||||
|
"_ym_d",
|
||||||
|
"_ym_isad",
|
||||||
|
"_ym_visorc",
|
||||||
|
"yandexuid",
|
||||||
|
"ymex",
|
||||||
|
].includes(key) ||
|
||||||
|
key.startsWith("_ga") ||
|
||||||
|
key.startsWith("_gid")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryString = result
|
||||||
|
.map(
|
||||||
|
([key, value]) =>
|
||||||
|
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
|
||||||
|
)
|
||||||
|
.join("&");
|
||||||
|
|
||||||
|
return queryString;
|
||||||
|
}
|
||||||
|
|
||||||
interface TrialPaymentTemplateProps {
|
interface TrialPaymentTemplateProps {
|
||||||
funnel: FunnelDefinition;
|
funnel: FunnelDefinition;
|
||||||
@ -58,7 +89,7 @@ export function TrialPaymentTemplate({
|
|||||||
screenProgress,
|
screenProgress,
|
||||||
defaultTexts,
|
defaultTexts,
|
||||||
}: TrialPaymentTemplateProps) {
|
}: TrialPaymentTemplateProps) {
|
||||||
const router = useRouter();
|
const token = useClientToken();
|
||||||
|
|
||||||
// TODO: выбрать корректный paymentId для этого экрана (ключ из backend), временно "main"
|
// TODO: выбрать корректный paymentId для этого экрана (ключ из backend), временно "main"
|
||||||
const paymentId = "main";
|
const paymentId = "main";
|
||||||
@ -76,17 +107,15 @@ export function TrialPaymentTemplate({
|
|||||||
const billingPeriod = placement?.billingPeriod;
|
const billingPeriod = placement?.billingPeriod;
|
||||||
const billingInterval = placement?.billingInterval || 1;
|
const billingInterval = placement?.billingInterval || 1;
|
||||||
const currency = placement?.currency || Currency.USD;
|
const currency = placement?.currency || Currency.USD;
|
||||||
|
const paymentUrl = placement?.paymentUrl || "";
|
||||||
console.log({ placement });
|
|
||||||
|
|
||||||
const handlePayClick = () => {
|
const handlePayClick = () => {
|
||||||
router.push(
|
const redirectUrl = `${paymentUrl}?paywallId=${paywallId}&placementId=${placementId}&productId=${productId}&jwtToken=${token}&price=${(
|
||||||
ROUTES.payment({
|
(trialPrice || 100) / 100
|
||||||
productId,
|
).toFixed(2)}¤cy=${currency}&${getTrackingCookiesForRedirect()}`;
|
||||||
placementId,
|
console.log("redirectUrl", redirectUrl);
|
||||||
paywallId,
|
|
||||||
})
|
// return window.location.replace(redirectUrl);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const paymentSectionRef = useRef<HTMLDivElement | null>(null);
|
const paymentSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -215,7 +244,7 @@ export function TrialPaymentTemplate({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading || !placement) {
|
if (isLoading || !placement || !token) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-h-dvh max-w-[560px] mx-auto flex items-center justify-center">
|
<div className="w-full min-h-dvh max-w-[560px] mx-auto flex items-center justify-center">
|
||||||
<Spinner className="size-8" />
|
<Spinner className="size-8" />
|
||||||
@ -239,7 +268,7 @@ export function TrialPaymentTemplate({
|
|||||||
{/* Header block */}
|
{/* Header block */}
|
||||||
{screen.headerBlock && (
|
{screen.headerBlock && (
|
||||||
<Header
|
<Header
|
||||||
className="mt-3"
|
className="mt-3 sticky top-[18px] z-30"
|
||||||
text={buildTypographyProps(screen.headerBlock.text, {
|
text={buildTypographyProps(screen.headerBlock.text, {
|
||||||
as: "p",
|
as: "p",
|
||||||
defaults: { font: "inter", weight: "semiBold", size: "sm" },
|
defaults: { font: "inter", weight: "semiBold", size: "sm" },
|
||||||
|
|||||||
@ -29,8 +29,7 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
true: "bg-gradient-to-r from-[#EBF5FF] to-[#DBEAFE] border-primary shadow-blue-glow-2 text-primary",
|
true: "bg-gradient-to-r from-[#EBF5FF] to-[#DBEAFE] border-primary shadow-blue-glow-2 text-primary",
|
||||||
false:
|
false: "bg-background border-border shadow-black-glow text-black",
|
||||||
"bg-background border-border shadow-black-glow text-black",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@ -64,6 +63,16 @@ function MainButton({
|
|||||||
data-slot="main-button"
|
data-slot="main-button"
|
||||||
className={cn(buttonVariants({ cornerRadius, active, className }))}
|
className={cn(buttonVariants({ cornerRadius, active, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
if (disabled) return;
|
||||||
|
const targetEl = e.target as HTMLElement | null;
|
||||||
|
const isCheckboxTarget = targetEl?.closest('[data-slot="checkbox"]');
|
||||||
|
if (isCheckboxTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
props.onClick?.(e);
|
||||||
|
}}
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<Label
|
<Label
|
||||||
@ -81,7 +90,13 @@ function MainButton({
|
|||||||
{...checkboxProps}
|
{...checkboxProps}
|
||||||
checked={active ?? false}
|
checked={active ?? false}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => {
|
||||||
|
if (disabled) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
props.onClick?.(
|
||||||
|
e as unknown as React.MouseEvent<HTMLButtonElement>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
|
|||||||
@ -76,7 +76,7 @@ const BottomActionButton = forwardRef<HTMLDivElement, BottomActionButtonProps>(
|
|||||||
<div
|
<div
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed bottom-0 left-1/2 -translate-x-1/2 w-full",
|
"fixed bottom-0 left-1/2 -translate-x-1/2 w-full z-10",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -12,3 +12,8 @@ export const setAuthTokenToCookie = async (token: string): Promise<void> => {
|
|||||||
maxAge: 60 * 60 * 24 * 365,
|
maxAge: 60 * 60 * 24 * 365,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAuthTokenFromCookie = async (): Promise<string | undefined> => {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
return cookieStore.get("accessToken")?.value;
|
||||||
|
};
|
||||||
|
|||||||
17
src/hooks/auth/useClientToken.ts
Normal file
17
src/hooks/auth/useClientToken.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { getAuthTokenFromCookie } from "@/entities/user/serverActions";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
export const useClientToken = () => {
|
||||||
|
const [token, setToken] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const token = await getAuthTokenFromCookie();
|
||||||
|
setToken(token);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return useMemo(() => token, [token]);
|
||||||
|
};
|
||||||
@ -100,8 +100,10 @@ class HttpClient {
|
|||||||
accessToken = await getServerAccessToken();
|
accessToken = await getServerAccessToken();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { getClientAccessToken } = await import("../auth/token");
|
const { getAuthTokenFromCookie } = await import(
|
||||||
accessToken = getClientAccessToken();
|
"@/entities/user/serverActions"
|
||||||
|
);
|
||||||
|
accessToken = await getAuthTokenFromCookie();
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user