Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e724f47ea0 | ||
|
|
79e903d35a | ||
|
|
e5d7b23d99 | ||
|
|
a60bada93f | ||
|
|
b863afe77c | ||
|
|
be0d0952fb | ||
|
|
2c2dc846d8 | ||
|
|
4f78f8bd9d | ||
|
|
ff5f022cf8 | ||
|
|
94da453d02 | ||
|
|
17e32430dc | ||
|
|
70e79962c9 | ||
|
|
9b5f82bc1e | ||
|
|
550b3fc98c | ||
|
|
f371c7266e | ||
|
|
16b8d10859 | ||
|
|
0766dbcac8 | ||
|
|
3379eab873 | ||
|
|
60edce97fe | ||
|
|
60cabbaa59 | ||
|
|
f1071ccb0d | ||
|
|
fefb1b74f8 | ||
|
|
5e51d2c2cf | ||
|
|
e992e01223 | ||
|
|
7981159da1 | ||
|
|
ed83a93afe | ||
|
|
51a610eae3 | ||
|
|
0fe7f4b454 | ||
|
|
b4ddcc574f |
@ -3,38 +3,6 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import { createPaymentCheckout } from "@/entities/payment/api";
|
import { createPaymentCheckout } from "@/entities/payment/api";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert base64 string to bytes (handles UTF-8 properly)
|
|
||||||
* MDN recommended approach: https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa#unicode_strings
|
|
||||||
*/
|
|
||||||
function base64ToBytes(base64: string): Uint8Array {
|
|
||||||
const binString = atob(base64);
|
|
||||||
return Uint8Array.from(binString, (m) => m.codePointAt(0) ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode URL-safe base64 state parameter to UTM object
|
|
||||||
* Supports UTF-8 encoded content (e.g., utm_campaign=夏セール)
|
|
||||||
*/
|
|
||||||
function decodeStateParam(state: string): Record<string, string> | undefined {
|
|
||||||
if (!state) return undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Restore URL-safe base64 to standard base64
|
|
||||||
let base64 = state.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
// Add padding if needed
|
|
||||||
while (base64.length % 4) {
|
|
||||||
base64 += "=";
|
|
||||||
}
|
|
||||||
// Decode base64 to bytes, then decode UTF-8
|
|
||||||
const bytes = base64ToBytes(base64);
|
|
||||||
const json = new TextDecoder().decode(bytes);
|
|
||||||
return JSON.parse(json);
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
const productId = req.nextUrl.searchParams.get("productId");
|
const productId = req.nextUrl.searchParams.get("productId");
|
||||||
const placementId = req.nextUrl.searchParams.get("placementId");
|
const placementId = req.nextUrl.searchParams.get("placementId");
|
||||||
@ -42,18 +10,11 @@ export async function GET(req: NextRequest) {
|
|||||||
const fbPixels = req.nextUrl.searchParams.get("fb_pixels");
|
const fbPixels = req.nextUrl.searchParams.get("fb_pixels");
|
||||||
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");
|
||||||
const sessionId = req.nextUrl.searchParams.get("sessionId");
|
|
||||||
const state = req.nextUrl.searchParams.get("state");
|
|
||||||
|
|
||||||
// Decode state param to get UTM
|
|
||||||
const utm = state ? decodeStateParam(state) : undefined;
|
|
||||||
|
|
||||||
const data = await createPaymentCheckout({
|
const data = await createPaymentCheckout({
|
||||||
productId: productId || "",
|
productId: productId || "",
|
||||||
placementId: placementId || "",
|
placementId: placementId || "",
|
||||||
paywallId: paywallId || "",
|
paywallId: paywallId || "",
|
||||||
sessionId: sessionId || undefined,
|
|
||||||
utm,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let redirectUrl: URL = new URL(data?.paymentUrl || "", req.nextUrl.origin);
|
let redirectUrl: URL = new URL(data?.paymentUrl || "", req.nextUrl.origin);
|
||||||
|
|||||||
@ -39,8 +39,6 @@ export async function GET(req: NextRequest) {
|
|||||||
const productPrice = searchParams.get("price");
|
const productPrice = searchParams.get("price");
|
||||||
const currency = searchParams.get("currency");
|
const currency = searchParams.get("currency");
|
||||||
const nextUrl = searchParams.get("nextUrl");
|
const nextUrl = searchParams.get("nextUrl");
|
||||||
const sessionId = searchParams.get("sessionId");
|
|
||||||
const state = searchParams.get("state");
|
|
||||||
|
|
||||||
const redirectUrl = new URL(
|
const redirectUrl = new URL(
|
||||||
`${nextUrl || ROUTES.payment()}`,
|
`${nextUrl || ROUTES.payment()}`,
|
||||||
@ -53,8 +51,6 @@ export async function GET(req: NextRequest) {
|
|||||||
if (fbPixels) redirectUrl.searchParams.set("fb_pixels", fbPixels);
|
if (fbPixels) redirectUrl.searchParams.set("fb_pixels", fbPixels);
|
||||||
if (productPrice) redirectUrl.searchParams.set("price", productPrice);
|
if (productPrice) redirectUrl.searchParams.set("price", productPrice);
|
||||||
if (currency) redirectUrl.searchParams.set("currency", currency);
|
if (currency) redirectUrl.searchParams.set("currency", currency);
|
||||||
if (sessionId) redirectUrl.searchParams.set("sessionId", sessionId);
|
|
||||||
if (state) redirectUrl.searchParams.set("state", state);
|
|
||||||
|
|
||||||
const trackingCookies = extractTrackingCookiesFromUrl(req.nextUrl);
|
const trackingCookies = extractTrackingCookiesFromUrl(req.nextUrl);
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { Button, Typography } from "@/components/ui";
|
|||||||
import { BlurComponent } from "@/components/widgets";
|
import { BlurComponent } from "@/components/widgets";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import { translatePathEmailMarketingCompatibilityV1 } from "@/shared/constants/translate";
|
import { translatePathEmailMarketingCompatibilityV1 } from "@/shared/constants/translate";
|
||||||
import { getCurrentUtmParams } from "@/shared/utils/url";
|
|
||||||
|
|
||||||
import styles from "./LandingButtonWrapper.module.scss";
|
import styles from "./LandingButtonWrapper.module.scss";
|
||||||
|
|
||||||
@ -18,8 +17,7 @@ export default function LandingButtonWrapper() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleContinue = () => {
|
const handleContinue = () => {
|
||||||
const utmParams = getCurrentUtmParams();
|
router.push(ROUTES.emailMarketingCompatibilityV1SpecialOffer());
|
||||||
router.push(ROUTES.emailMarketingCompatibilityV1SpecialOffer(utmParams));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,10 +7,6 @@ import { Button, Typography } from "@/components/ui";
|
|||||||
import { BlurComponent } from "@/components/widgets";
|
import { BlurComponent } from "@/components/widgets";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import { translatePathEmailMarketingCompatibilityV1 } from "@/shared/constants/translate";
|
import { translatePathEmailMarketingCompatibilityV1 } from "@/shared/constants/translate";
|
||||||
import {
|
|
||||||
getSessionIdFromUrl,
|
|
||||||
getStateParamForRedirect,
|
|
||||||
} from "@/shared/utils/url";
|
|
||||||
|
|
||||||
import styles from "./SpecialOfferButtonWrapper.module.scss";
|
import styles from "./SpecialOfferButtonWrapper.module.scss";
|
||||||
|
|
||||||
@ -31,20 +27,13 @@ export default function SpecialOfferButtonWrapper({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const openPaymentModal = () => {
|
const openPaymentModal = () => {
|
||||||
const state = getStateParamForRedirect();
|
router.push(
|
||||||
const sessionId = getSessionIdFromUrl();
|
ROUTES.payment({
|
||||||
const params: Record<string, string> = {
|
productId,
|
||||||
productId,
|
placementId,
|
||||||
placementId,
|
paywallId,
|
||||||
paywallId,
|
})
|
||||||
};
|
);
|
||||||
if (state) {
|
|
||||||
params.state = state;
|
|
||||||
}
|
|
||||||
if (sessionId) {
|
|
||||||
params.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
router.push(ROUTES.payment(params));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { Button, Typography } from "@/components/ui";
|
|||||||
import { BlurComponent } from "@/components/widgets";
|
import { BlurComponent } from "@/components/widgets";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import { translatePathEmailMarketingSoulmateV1 } from "@/shared/constants/translate";
|
import { translatePathEmailMarketingSoulmateV1 } from "@/shared/constants/translate";
|
||||||
import { getCurrentUtmParams } from "@/shared/utils/url";
|
|
||||||
|
|
||||||
import styles from "./LandingButtonWrapper.module.scss";
|
import styles from "./LandingButtonWrapper.module.scss";
|
||||||
|
|
||||||
@ -16,8 +15,7 @@ export default function LandingButtonWrapper() {
|
|||||||
const t = useTranslations(translatePathEmailMarketingSoulmateV1("Landing"));
|
const t = useTranslations(translatePathEmailMarketingSoulmateV1("Landing"));
|
||||||
|
|
||||||
const handleContinue = () => {
|
const handleContinue = () => {
|
||||||
const utmParams = getCurrentUtmParams();
|
router.push(ROUTES.emailMarketingSoulmateV1SpecialOffer());
|
||||||
router.push(ROUTES.emailMarketingSoulmateV1SpecialOffer(utmParams));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,10 +7,6 @@ import { Button, Typography } from "@/components/ui";
|
|||||||
import { BlurComponent } from "@/components/widgets";
|
import { BlurComponent } from "@/components/widgets";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import { translatePathEmailMarketingSoulmateV1 } from "@/shared/constants/translate";
|
import { translatePathEmailMarketingSoulmateV1 } from "@/shared/constants/translate";
|
||||||
import {
|
|
||||||
getSessionIdFromUrl,
|
|
||||||
getStateParamForRedirect,
|
|
||||||
} from "@/shared/utils/url";
|
|
||||||
|
|
||||||
import styles from "./SpecialOfferButtonWrapper.module.scss";
|
import styles from "./SpecialOfferButtonWrapper.module.scss";
|
||||||
|
|
||||||
@ -31,20 +27,13 @@ export default function SpecialOfferButtonWrapper({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const openPaymentModal = () => {
|
const openPaymentModal = () => {
|
||||||
const state = getStateParamForRedirect();
|
router.push(
|
||||||
const sessionId = getSessionIdFromUrl();
|
ROUTES.payment({
|
||||||
const params: Record<string, string> = {
|
productId,
|
||||||
productId,
|
placementId,
|
||||||
placementId,
|
paywallId,
|
||||||
paywallId,
|
})
|
||||||
};
|
);
|
||||||
if (state) {
|
|
||||||
params.state = state;
|
|
||||||
}
|
|
||||||
if (sessionId) {
|
|
||||||
params.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
router.push(ROUTES.payment(params));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { useTranslations } from "next-intl";
|
|||||||
import { Button, Typography } from "@/components/ui";
|
import { Button, Typography } from "@/components/ui";
|
||||||
import { usePeriod } from "@/hooks/translations/usePeriod";
|
import { usePeriod } from "@/hooks/translations/usePeriod";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import { getCurrentUtmParams } from "@/shared/utils/url";
|
|
||||||
import { PeriodType } from "@/types/period";
|
import { PeriodType } from "@/types/period";
|
||||||
|
|
||||||
import styles from "./Button.module.scss";
|
import styles from "./Button.module.scss";
|
||||||
@ -25,8 +24,7 @@ export default function SaveOffButton({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
const utmParams = getCurrentUtmParams();
|
router.push(ROUTES.secretDiscount());
|
||||||
router.push(ROUTES.secretDiscount(utmParams));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -6,10 +6,6 @@ import { useTranslations } from "next-intl";
|
|||||||
import { Button, Typography } from "@/components/ui";
|
import { Button, Typography } from "@/components/ui";
|
||||||
import { usePeriod } from "@/hooks/translations/usePeriod";
|
import { usePeriod } from "@/hooks/translations/usePeriod";
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import {
|
|
||||||
getSessionIdFromUrl,
|
|
||||||
getStateParamForRedirect,
|
|
||||||
} from "@/shared/utils/url";
|
|
||||||
import { PeriodType } from "@/types/period";
|
import { PeriodType } from "@/types/period";
|
||||||
|
|
||||||
import styles from "./Button.module.scss";
|
import styles from "./Button.module.scss";
|
||||||
@ -34,20 +30,13 @@ export default function SecretDiscountButton({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
const state = getStateParamForRedirect();
|
router.push(
|
||||||
const sessionId = getSessionIdFromUrl();
|
ROUTES.payment({
|
||||||
const params: Record<string, string> = {
|
productId,
|
||||||
productId,
|
placementId,
|
||||||
placementId,
|
paywallId,
|
||||||
paywallId,
|
})
|
||||||
};
|
);
|
||||||
if (state) {
|
|
||||||
params.state = state;
|
|
||||||
}
|
|
||||||
if (sessionId) {
|
|
||||||
params.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
router.push(ROUTES.payment(params));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -4,8 +4,6 @@ export const CheckoutRequestSchema = z.object({
|
|||||||
productId: z.string(),
|
productId: z.string(),
|
||||||
placementId: z.string(),
|
placementId: z.string(),
|
||||||
paywallId: z.string(),
|
paywallId: z.string(),
|
||||||
sessionId: z.string().optional(),
|
|
||||||
utm: z.record(z.string()).optional(),
|
|
||||||
});
|
});
|
||||||
export type CheckoutRequest = z.infer<typeof CheckoutRequestSchema>;
|
export type CheckoutRequest = z.infer<typeof CheckoutRequestSchema>;
|
||||||
|
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const UserSchema = z.object({
|
|||||||
signDate: z.string().nullable().optional(),
|
signDate: z.string().nullable().optional(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
externalId: z.string().optional(),
|
externalId: z.string().optional(),
|
||||||
klaviyoId: z.string().nullable().optional(),
|
klaviyoId: z.string().nullable(),
|
||||||
assistants: z.array(z.string()),
|
assistants: z.array(z.string()),
|
||||||
createdAt: z.string().optional(),
|
createdAt: z.string().optional(),
|
||||||
updatedAt: z.string(),
|
updatedAt: z.string(),
|
||||||
|
|||||||
@ -74,10 +74,8 @@ export const ROUTES = {
|
|||||||
paymentFailed: () => createRoute(["payment", "failed"]),
|
paymentFailed: () => createRoute(["payment", "failed"]),
|
||||||
|
|
||||||
// Secret Discount
|
// Secret Discount
|
||||||
saveOff: (queryParams?: Record<string, string>) =>
|
saveOff: () => createRoute(["save-off"]),
|
||||||
createRoute(["save-off"], queryParams),
|
secretDiscount: () => createRoute(["secret-discount"]),
|
||||||
secretDiscount: (queryParams?: Record<string, string>) =>
|
|
||||||
createRoute(["secret-discount"], queryParams),
|
|
||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
chat: (id?: string) => createRoute(["chat", id]),
|
chat: (id?: string) => createRoute(["chat", id]),
|
||||||
@ -88,16 +86,16 @@ export const ROUTES = {
|
|||||||
additionalPurchases: (type?: string) => createRoute(["ap", type]),
|
additionalPurchases: (type?: string) => createRoute(["ap", type]),
|
||||||
|
|
||||||
// Email Marketing Compatibility V1
|
// Email Marketing Compatibility V1
|
||||||
emailMarketingCompatibilityV1Landing: (queryParams?: Record<string, string>) =>
|
emailMarketingCompatibilityV1Landing: () =>
|
||||||
createRoute([emailMarketingCompatibilityV1Prefix, "landing"], queryParams),
|
createRoute([emailMarketingCompatibilityV1Prefix, "landing"]),
|
||||||
emailMarketingCompatibilityV1SpecialOffer: (queryParams?: Record<string, string>) =>
|
emailMarketingCompatibilityV1SpecialOffer: () =>
|
||||||
createRoute([emailMarketingCompatibilityV1Prefix, "special-offer"], queryParams),
|
createRoute([emailMarketingCompatibilityV1Prefix, "special-offer"]),
|
||||||
|
|
||||||
// Email Marketing Soulmate V1
|
// Email Marketing Soulmate V1
|
||||||
emailMarketingSoulmateV1Landing: (queryParams?: Record<string, string>) =>
|
emailMarketingSoulmateV1Landing: () =>
|
||||||
createRoute([emailMarketingSoulmateV1Prefix, "landing"], queryParams),
|
createRoute([emailMarketingSoulmateV1Prefix, "landing"]),
|
||||||
emailMarketingSoulmateV1SpecialOffer: (queryParams?: Record<string, string>) =>
|
emailMarketingSoulmateV1SpecialOffer: () =>
|
||||||
createRoute([emailMarketingSoulmateV1Prefix, "special-offer"], queryParams),
|
createRoute([emailMarketingSoulmateV1Prefix, "special-offer"]),
|
||||||
|
|
||||||
// // Compatibility
|
// // Compatibility
|
||||||
// compatibilities: () => createRoute(["compatibilities"]),
|
// compatibilities: () => createRoute(["compatibilities"]),
|
||||||
|
|||||||
@ -1,132 +0,0 @@
|
|||||||
// Params that should NOT be included in state (they are passed separately)
|
|
||||||
const EXCLUDED_STATE_PARAMS = [
|
|
||||||
"paywallId",
|
|
||||||
"placementId",
|
|
||||||
"productId",
|
|
||||||
"jwtToken",
|
|
||||||
"price",
|
|
||||||
"currency",
|
|
||||||
"fb_pixels",
|
|
||||||
"sessionId",
|
|
||||||
"state",
|
|
||||||
// Tracking cookies (passed separately)
|
|
||||||
"_fbc",
|
|
||||||
"_fbp",
|
|
||||||
"_ym_uid",
|
|
||||||
"_ym_d",
|
|
||||||
"_ym_isad",
|
|
||||||
"_ym_visorc",
|
|
||||||
"yandexuid",
|
|
||||||
"ymex",
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse current query params from URL
|
|
||||||
*/
|
|
||||||
export const parseQueryParams = (): Record<string, string> => {
|
|
||||||
if (typeof window === "undefined") return {};
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const result: Record<string, string> = {};
|
|
||||||
|
|
||||||
for (const [key, value] of params.entries()) {
|
|
||||||
result[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current query params that should be passed between screens and to payment
|
|
||||||
* Includes ALL params except internal ones (productId, placementId, etc.)
|
|
||||||
* Works with utm_*, fbclid, gclid, and any other marketing params
|
|
||||||
*/
|
|
||||||
export const getCurrentQueryParams = (): Record<string, string> => {
|
|
||||||
if (typeof window === "undefined") return {};
|
|
||||||
|
|
||||||
const params = parseQueryParams();
|
|
||||||
const utmParams: Record<string, string> = {};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
const isExcluded =
|
|
||||||
EXCLUDED_STATE_PARAMS.includes(key) ||
|
|
||||||
key.startsWith("_ga") ||
|
|
||||||
key.startsWith("_gid");
|
|
||||||
|
|
||||||
if (!isExcluded && value) {
|
|
||||||
utmParams[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return utmParams;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert bytes to base64 string (handles UTF-8 properly)
|
|
||||||
* MDN recommended approach: https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa#unicode_strings
|
|
||||||
*/
|
|
||||||
const bytesToBase64 = (bytes: Uint8Array): string => {
|
|
||||||
const binString = Array.from(bytes, (byte) =>
|
|
||||||
String.fromCodePoint(byte)
|
|
||||||
).join("");
|
|
||||||
return btoa(binString);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode params as base64 JSON for state parameter
|
|
||||||
* Uses URL-safe base64 encoding with UTF-8 support
|
|
||||||
* Handles Unicode characters (e.g., utm_campaign=夏セール)
|
|
||||||
*/
|
|
||||||
export const encodeStateParam = (params: Record<string, string>): string => {
|
|
||||||
if (Object.keys(params).length === 0) return "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const json = JSON.stringify(params);
|
|
||||||
// Encode string as UTF-8 bytes, then convert to base64
|
|
||||||
const bytes = new TextEncoder().encode(json);
|
|
||||||
const base64 = bytesToBase64(bytes)
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=+$/, "");
|
|
||||||
return base64;
|
|
||||||
} catch {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get base64-encoded state parameter with current query params
|
|
||||||
*/
|
|
||||||
export const getStateParamForRedirect = (): string => {
|
|
||||||
const params = getCurrentQueryParams();
|
|
||||||
return encodeStateParam(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sessionId from current URL if present
|
|
||||||
*/
|
|
||||||
export const getSessionIdFromUrl = (): string | undefined => {
|
|
||||||
if (typeof window === "undefined") return undefined;
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
return params.get("sessionId") || undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build URL with current query params preserved
|
|
||||||
*/
|
|
||||||
export const buildUrlWithQueryParams = (baseUrl: string): string => {
|
|
||||||
const params = getCurrentQueryParams();
|
|
||||||
|
|
||||||
if (Object.keys(params).length === 0) return baseUrl;
|
|
||||||
|
|
||||||
const url = new URL(baseUrl, window.location.origin);
|
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
url.searchParams.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.pathname + url.search;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Backward compatibility aliases
|
|
||||||
export const getCurrentUtmParams = getCurrentQueryParams;
|
|
||||||
export const buildUrlWithUtmParams = buildUrlWithQueryParams;
|
|
||||||
Loading…
Reference in New Issue
Block a user