auto open checkout modal

This commit is contained in:
miloschwartz
2026-02-04 21:31:46 -08:00
parent 11408c2656
commit c63589b204
3 changed files with 35 additions and 8 deletions

View File

@@ -10,8 +10,8 @@ import { Badge } from "./ui/badge";
import moment from "moment"; import moment from "moment";
import { DataTable } from "./ui/data-table"; import { DataTable } from "./ui/data-table";
import { GeneratedLicenseKey } from "@server/routers/generatedLicense/types"; import { GeneratedLicenseKey } from "@server/routers/generatedLicense/types";
import { useState } from "react"; import { useState, useEffect } from "react";
import { useRouter } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
@@ -29,12 +29,15 @@ function obfuscateLicenseKey(key: string): string {
return `${firstPart}••••••••••••••••••••${lastPart}`; return `${firstPart}••••••••••••••••••••${lastPart}`;
} }
const GENERATE_QUERY = "generate";
export default function GenerateLicenseKeysTable({ export default function GenerateLicenseKeysTable({
licenseKeys, licenseKeys,
orgId orgId
}: GnerateLicenseKeysTableProps) { }: GnerateLicenseKeysTableProps) {
const t = useTranslations(); const t = useTranslations();
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams();
const { env } = useEnvContext(); const { env } = useEnvContext();
const api = createApiClient({ env }); const api = createApiClient({ env });
@@ -42,6 +45,19 @@ export default function GenerateLicenseKeysTable({
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [showGenerateForm, setShowGenerateForm] = useState(false); const [showGenerateForm, setShowGenerateForm] = useState(false);
useEffect(() => {
if (searchParams.get(GENERATE_QUERY) !== null) {
setShowGenerateForm(true);
const next = new URLSearchParams(searchParams);
next.delete(GENERATE_QUERY);
const qs = next.toString();
const url = qs
? `${window.location.pathname}?${qs}`
: window.location.pathname;
window.history.replaceState(null, "", url);
}
}, [searchParams]);
const handleLicenseGenerated = () => { const handleLicenseGenerated = () => {
// Refresh the data after license is generated // Refresh the data after license is generated
refreshData(); refreshData();

View File

@@ -1,6 +1,8 @@
type CleanRedirectOptions = { type CleanRedirectOptions = {
fallback?: string; fallback?: string;
maxRedirectDepth?: number; maxRedirectDepth?: number;
/** When true, preserve all query params on the path (for internal redirects). Default false. */
allowAllQueryParams?: boolean;
}; };
const ALLOWED_QUERY_PARAMS = new Set([ const ALLOWED_QUERY_PARAMS = new Set([
@@ -16,14 +18,18 @@ export function cleanRedirect(
input: string, input: string,
options: CleanRedirectOptions = {} options: CleanRedirectOptions = {}
): string { ): string {
const { fallback = "/", maxRedirectDepth = 2 } = options; const {
fallback = "/",
maxRedirectDepth = 2,
allowAllQueryParams = false
} = options;
if (!input || typeof input !== "string") { if (!input || typeof input !== "string") {
return fallback; return fallback;
} }
try { try {
return sanitizeUrl(input, fallback, maxRedirectDepth); return sanitizeUrl(input, fallback, maxRedirectDepth, allowAllQueryParams);
} catch { } catch {
return fallback; return fallback;
} }
@@ -32,7 +38,8 @@ export function cleanRedirect(
function sanitizeUrl( function sanitizeUrl(
input: string, input: string,
fallback: string, fallback: string,
remainingRedirectDepth: number remainingRedirectDepth: number,
allowAllQueryParams: boolean = false
): string { ): string {
if ( if (
input.startsWith("javascript:") || input.startsWith("javascript:") ||
@@ -56,7 +63,7 @@ function sanitizeUrl(
const cleanParams = new URLSearchParams(); const cleanParams = new URLSearchParams();
for (const [key, value] of url.searchParams.entries()) { for (const [key, value] of url.searchParams.entries()) {
if (!ALLOWED_QUERY_PARAMS.has(key)) { if (!allowAllQueryParams && !ALLOWED_QUERY_PARAMS.has(key)) {
continue; continue;
} }
@@ -68,7 +75,8 @@ function sanitizeUrl(
const cleanedRedirect = sanitizeUrl( const cleanedRedirect = sanitizeUrl(
value, value,
"", "",
remainingRedirectDepth - 1 remainingRedirectDepth - 1,
allowAllQueryParams
); );
if (cleanedRedirect) { if (cleanedRedirect) {

View File

@@ -28,7 +28,10 @@ export function consumeInternalRedirectPath(): string | null {
return null; return null;
} }
const cleaned = cleanRedirect(storedPath, { fallback: "" }); const cleaned = cleanRedirect(storedPath, {
fallback: "",
allowAllQueryParams: true
});
if (!cleaned) return null; if (!cleaned) return null;
return cleaned.startsWith("/") ? cleaned : `/${cleaned}`; return cleaned.startsWith("/") ? cleaned : `/${cleaned}`;