AW-503-additional-purchases
redirect when single payment, return_url & pageUrl
This commit is contained in:
parent
05a3e9f8ae
commit
fe4ed88cf8
@ -6,6 +6,7 @@ import { Button, Spinner, Typography } from "@/components/ui";
|
||||
import { BlurComponent } from "@/components/widgets";
|
||||
import { useSingleCheckout } from "@/hooks/payment/useSingleCheckout";
|
||||
import { useToast } from "@/providers/toast-provider";
|
||||
import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import { useMultiPageNavigationContext } from "..";
|
||||
|
||||
@ -30,6 +31,10 @@ export default function AddConsultantButton() {
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
returnUrl: new URL(
|
||||
navigation.getNextPageUrl() || ROUTES.home(),
|
||||
process.env.NEXT_PUBLIC_APP_URL || ""
|
||||
).toString(),
|
||||
});
|
||||
|
||||
const handleGetConsultation = () => {
|
||||
|
||||
@ -6,6 +6,7 @@ import { Button, Spinner, Typography } from "@/components/ui";
|
||||
import { BlurComponent } from "@/components/widgets";
|
||||
import { useSingleCheckout } from "@/hooks/payment/useSingleCheckout";
|
||||
import { useToast } from "@/providers/toast-provider";
|
||||
import { ROUTES } from "@/shared/constants/client-routes";
|
||||
|
||||
import { useMultiPageNavigationContext } from "..";
|
||||
import { useProductSelection } from "../ProductSelectionProvider";
|
||||
@ -29,6 +30,10 @@ export default function AddGuidesButton() {
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
returnUrl: new URL(
|
||||
navigation.getNextPageUrl() || ROUTES.home(),
|
||||
process.env.NEXT_PUBLIC_APP_URL || ""
|
||||
).toString(),
|
||||
});
|
||||
|
||||
const handlePurchase = () => {
|
||||
|
||||
@ -34,9 +34,10 @@ export function MultiPageNavigationProvider({
|
||||
data,
|
||||
currentType,
|
||||
getTypeFromItem: item => item.type ?? "",
|
||||
navigateToItemByType: type => {
|
||||
router.push(ROUTES.additionalPurchases(type));
|
||||
},
|
||||
// navigateToItemByType: type => {
|
||||
// router.push(ROUTES.additionalPurchases(type));
|
||||
// },
|
||||
getPageUrlByItem: item => ROUTES.additionalPurchases(item.type ?? ""),
|
||||
onComplete: () => {
|
||||
router.push(ROUTES.home());
|
||||
},
|
||||
|
||||
@ -34,6 +34,7 @@ export default function RefillOptionsModal({
|
||||
const { handleSingleCheckout, isLoading } = useSingleCheckout({
|
||||
onSuccess: onPaymentSuccess,
|
||||
onError: onPaymentError,
|
||||
returnUrl: typeof window !== "undefined" ? window.location.href : "",
|
||||
});
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState<IRefillModalsProduct>(
|
||||
|
||||
@ -37,6 +37,7 @@ export default function RefillTimerModal({
|
||||
const { handleSingleCheckout, isLoading } = useSingleCheckout({
|
||||
onSuccess: onPaymentSuccess,
|
||||
onError: onPaymentError,
|
||||
returnUrl: typeof window !== "undefined" ? window.location.href : "",
|
||||
});
|
||||
|
||||
const { seconds, isFinished } = useTimer({
|
||||
|
||||
@ -25,7 +25,10 @@ export {
|
||||
export { default as RefillOptionsModal } from "./CreditsModals/RefillOptionsModal/RefillOptionsModal";
|
||||
export { default as RefillTimerModal } from "./CreditsModals/RefillTimerModal/RefillTimerModal";
|
||||
export { default as GlobalNewMessagesBanner } from "./GlobalNewMessagesBanner/GlobalNewMessagesBanner";
|
||||
export { default as LastMessagePreview, type LastMessagePreviewProps } from "./LastMessagePreview/LastMessagePreview";
|
||||
export {
|
||||
default as LastMessagePreview,
|
||||
type LastMessagePreviewProps,
|
||||
} from "./LastMessagePreview/LastMessagePreview";
|
||||
export { default as MessageInput } from "./MessageInput/MessageInput";
|
||||
export { default as MessageInputWrapper } from "./MessageInputWrapper/MessageInputWrapper";
|
||||
export { default as NewMessages } from "./NewMessages/NewMessages";
|
||||
|
||||
@ -4,7 +4,10 @@ import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Skeleton } from "@/components/ui";
|
||||
import { getMyChatSettings, updateMyChatSettings } from "@/entities/chats/chatSettings.api";
|
||||
import {
|
||||
getMyChatSettings,
|
||||
updateMyChatSettings,
|
||||
} from "@/entities/chats/chatSettings.api";
|
||||
import type { IChatSettings } from "@/entities/chats/types";
|
||||
|
||||
import styles from "./AutoTopUpToggle.module.scss";
|
||||
|
||||
@ -14,16 +14,17 @@ import {
|
||||
/**
|
||||
* Fetch current user's chat settings (client-side)
|
||||
*/
|
||||
export const getMyChatSettings = async (): Promise<IGetMyChatSettingsResponse> => {
|
||||
return http.get<IGetMyChatSettingsResponse>(
|
||||
API_ROUTES.getMyChatSettings(),
|
||||
{
|
||||
tags: ["profile", "chat-settings"],
|
||||
schema: GetMyChatSettingsResponseSchema,
|
||||
revalidate: 0,
|
||||
}
|
||||
);
|
||||
};
|
||||
export const getMyChatSettings =
|
||||
async (): Promise<IGetMyChatSettingsResponse> => {
|
||||
return http.get<IGetMyChatSettingsResponse>(
|
||||
API_ROUTES.getMyChatSettings(),
|
||||
{
|
||||
tags: ["profile", "chat-settings"],
|
||||
schema: GetMyChatSettingsResponseSchema,
|
||||
revalidate: 0,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update current user's chat settings (client-side)
|
||||
@ -41,4 +42,3 @@ export const updateMyChatSettings = async (
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -97,4 +97,3 @@ export {
|
||||
GetMyChatSettingsResponseSchema,
|
||||
UpdateMyChatSettingsResponseSchema,
|
||||
};
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ export type PaymentInfo = z.infer<typeof PaymentInfoSchema>;
|
||||
export const SingleCheckoutRequestSchema = z.object({
|
||||
paymentInfo: PaymentInfoSchema,
|
||||
return_url: z.string().optional(),
|
||||
pageUrl: z.string().optional(),
|
||||
});
|
||||
export type SingleCheckoutRequest = z.infer<typeof SingleCheckoutRequestSchema>;
|
||||
|
||||
@ -31,6 +32,7 @@ export const SingleCheckoutSuccessSchema = z.object({
|
||||
payment: z.object({
|
||||
status: z.string(),
|
||||
invoiceId: z.string(),
|
||||
paymentUrl: z.string().url().optional(),
|
||||
}),
|
||||
});
|
||||
export type SingleCheckoutSuccess = z.infer<typeof SingleCheckoutSuccessSchema>;
|
||||
|
||||
@ -114,15 +114,17 @@ export const useChatSocket = (
|
||||
}, [emit, chatId]);
|
||||
|
||||
// Auto top-up: use existing single checkout flow
|
||||
const { handleSingleCheckout, isLoading: isAutoTopUpLoading } = useSingleCheckout({
|
||||
onSuccess: fetchBalance,
|
||||
onError: () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Auto top-up payment failed");
|
||||
// Release in-flight lock on error so a future event can retry
|
||||
autoTopUpInProgressRef.current = false;
|
||||
},
|
||||
});
|
||||
const { handleSingleCheckout, isLoading: isAutoTopUpLoading } =
|
||||
useSingleCheckout({
|
||||
onSuccess: fetchBalance,
|
||||
onError: () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Auto top-up payment failed");
|
||||
// Release in-flight lock on error so a future event can retry
|
||||
autoTopUpInProgressRef.current = false;
|
||||
},
|
||||
returnUrl: typeof window !== "undefined" ? window.location.href : "",
|
||||
});
|
||||
|
||||
// Auto top-up: silent flow (no UI prompt)
|
||||
const autoTopUpInProgressRef = useRef(false);
|
||||
@ -222,7 +224,6 @@ export const useChatSocket = (
|
||||
handleSingleCheckout(r.data);
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!session?.maxFinishedAt) return;
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface PageNavigationOptions<T> {
|
||||
data: T[];
|
||||
currentType: string;
|
||||
getTypeFromItem: (item: T) => string;
|
||||
navigateToItemByType: (type: string) => void;
|
||||
// navigateToItemByType: (type: string) => void;
|
||||
getPageUrlByItem: (item: T) => string;
|
||||
onBeforeNext?: (nextItem: T) => boolean | Promise<boolean>;
|
||||
onBeforePrevious?: (prevItem: T) => boolean | Promise<boolean>;
|
||||
onComplete: () => void;
|
||||
@ -25,6 +27,7 @@ interface PageNavigationReturn<T> {
|
||||
goToFirst: () => Promise<void>;
|
||||
goToLast: () => Promise<void>;
|
||||
goToIndex: (index: number) => Promise<void>;
|
||||
getNextPageUrl: () => string | undefined;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
@ -32,18 +35,22 @@ export function useMultiPageNavigation<T>({
|
||||
data,
|
||||
currentType,
|
||||
getTypeFromItem,
|
||||
navigateToItemByType,
|
||||
// navigateToItemByType,
|
||||
getPageUrlByItem,
|
||||
onBeforeNext,
|
||||
onBeforePrevious,
|
||||
onComplete,
|
||||
onStart,
|
||||
}: PageNavigationOptions<T>): PageNavigationReturn<T> {
|
||||
const router = useRouter();
|
||||
|
||||
const currentIndex = useMemo(
|
||||
() => data.findIndex(item => getTypeFromItem(item) === currentType),
|
||||
[data, currentType, getTypeFromItem]
|
||||
);
|
||||
|
||||
const currentItem = useMemo(() => data[currentIndex], [data, currentIndex]);
|
||||
const nextItem = useMemo(() => data[currentIndex + 1], [data, currentIndex]);
|
||||
|
||||
const isFirst = currentIndex === 0;
|
||||
const isLast = currentIndex === data.length - 1;
|
||||
@ -53,10 +60,11 @@ export function useMultiPageNavigation<T>({
|
||||
|
||||
const navigateToItem = useCallback(
|
||||
async (item: T) => {
|
||||
const type = getTypeFromItem(item);
|
||||
navigateToItemByType(type);
|
||||
// const type = getTypeFromItem(item);
|
||||
// navigateToItemByType(type);
|
||||
router.push(getPageUrlByItem(item));
|
||||
},
|
||||
[navigateToItemByType, getTypeFromItem]
|
||||
[getPageUrlByItem, router]
|
||||
);
|
||||
|
||||
const goToNext = useCallback(async () => {
|
||||
@ -109,9 +117,15 @@ export function useMultiPageNavigation<T>({
|
||||
[data, currentIndex, navigateToItem]
|
||||
);
|
||||
|
||||
const getNextPageUrl = useCallback(() => {
|
||||
if (!hasNext || !nextItem) return;
|
||||
return getPageUrlByItem(nextItem);
|
||||
}, [getPageUrlByItem, hasNext, nextItem]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
currentItem,
|
||||
nextItem,
|
||||
currentIndex,
|
||||
isFirst,
|
||||
isLast,
|
||||
@ -122,10 +136,13 @@ export function useMultiPageNavigation<T>({
|
||||
goToFirst,
|
||||
goToLast,
|
||||
goToIndex,
|
||||
getTypeFromItem,
|
||||
getNextPageUrl,
|
||||
totalPages,
|
||||
}),
|
||||
[
|
||||
currentItem,
|
||||
nextItem,
|
||||
currentIndex,
|
||||
isFirst,
|
||||
isLast,
|
||||
@ -136,6 +153,8 @@ export function useMultiPageNavigation<T>({
|
||||
goToFirst,
|
||||
goToLast,
|
||||
goToIndex,
|
||||
getTypeFromItem,
|
||||
getNextPageUrl,
|
||||
totalPages,
|
||||
]
|
||||
);
|
||||
|
||||
@ -6,6 +6,7 @@ import { performSingleCheckout } from "@/entities/payment/actions";
|
||||
import { PaymentInfo, SingleCheckoutRequest } from "@/entities/payment/types";
|
||||
|
||||
interface UseSingleCheckoutOptions {
|
||||
returnUrl?: string;
|
||||
onSuccess?: () => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
@ -13,7 +14,7 @@ interface UseSingleCheckoutOptions {
|
||||
export function useSingleCheckout(options: UseSingleCheckoutOptions = {}) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { onSuccess, onError } = options;
|
||||
const { returnUrl, onSuccess, onError } = options;
|
||||
|
||||
const handleSingleCheckout = useCallback(
|
||||
async (paymentInfo: PaymentInfo) => {
|
||||
@ -24,6 +25,8 @@ export function useSingleCheckout(options: UseSingleCheckoutOptions = {}) {
|
||||
try {
|
||||
const payload: SingleCheckoutRequest = {
|
||||
paymentInfo,
|
||||
pageUrl: typeof window !== "undefined" ? window.location.href : "",
|
||||
return_url: returnUrl,
|
||||
};
|
||||
|
||||
const response = await performSingleCheckout(payload);
|
||||
@ -39,7 +42,11 @@ export function useSingleCheckout(options: UseSingleCheckoutOptions = {}) {
|
||||
}
|
||||
|
||||
if ("payment" in response.data) {
|
||||
const { status } = response.data.payment;
|
||||
const { status, paymentUrl } = response.data.payment;
|
||||
|
||||
if (paymentUrl) {
|
||||
return window.location.replace(paymentUrl);
|
||||
}
|
||||
|
||||
if (status === "paid") {
|
||||
onSuccess?.();
|
||||
@ -58,7 +65,7 @@ export function useSingleCheckout(options: UseSingleCheckoutOptions = {}) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[onSuccess, onError, isLoading]
|
||||
[isLoading, returnUrl, onError, onSuccess]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
|
||||
@ -8,10 +8,10 @@ export async function getServerAccessToken() {
|
||||
export function getClientAccessToken(): string | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
|
||||
const cookies = document.cookie.split(';');
|
||||
const cookies = document.cookie.split(";");
|
||||
const accessTokenCookie = cookies.find(cookie =>
|
||||
cookie.trim().startsWith('accessToken=')
|
||||
cookie.trim().startsWith("accessToken=")
|
||||
);
|
||||
|
||||
return accessTokenCookie?.split('=')[1];
|
||||
return accessTokenCookie?.split("=")[1];
|
||||
}
|
||||
|
||||
@ -42,6 +42,6 @@ export const API_ROUTES = {
|
||||
createRoute(["chats", chatId, "messages"]),
|
||||
getUserBalance: () => createRoute(["chats", "balance"]),
|
||||
getMyChatSettings: () => createRoute(["chats", "profile", "chat-settings"]),
|
||||
updateMyChatSettings: () => createRoute(["chats", "profile", "chat-settings"]),
|
||||
updateMyChatSettings: () =>
|
||||
createRoute(["chats", "profile", "chat-settings"]),
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user