Migrate billing from Paddle to Lemon Squeezy
This commit is contained in:
@@ -557,7 +557,7 @@ export type DataExportSummary = {
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type PaddleTransactionSummary = {
|
||||
export type LemonSqueezyOrderSummary = {
|
||||
id: string | null;
|
||||
status: string | null;
|
||||
amount: number | null;
|
||||
@@ -1125,7 +1125,7 @@ export function normalizeTenantPackage(pkg: JsonValue): TenantPackageSummary {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePaddleTransaction(entry: JsonValue): PaddleTransactionSummary {
|
||||
function normalizeLemonSqueezyOrder(entry: JsonValue): LemonSqueezyOrderSummary {
|
||||
const amountValue = entry.amount ?? entry.grand_total ?? (entry.totals && entry.totals.grand_total);
|
||||
const taxValue = entry.tax ?? (entry.totals && entry.totals.tax_total);
|
||||
|
||||
@@ -2348,8 +2348,8 @@ export type Package = {
|
||||
gallery_days: number | null;
|
||||
max_events_per_year?: number | null;
|
||||
included_package_slug?: string | null;
|
||||
paddle_price_id?: string | null;
|
||||
paddle_product_id?: string | null;
|
||||
lemonsqueezy_variant_id?: string | null;
|
||||
lemonsqueezy_product_id?: string | null;
|
||||
branding_allowed?: boolean | null;
|
||||
watermark_allowed?: boolean | null;
|
||||
features: string[] | Record<string, boolean> | null;
|
||||
@@ -2731,8 +2731,8 @@ export async function downloadTenantDataExport(downloadUrl: string): Promise<Blo
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
export async function getTenantPaddleTransactions(cursor?: string): Promise<{
|
||||
data: PaddleTransactionSummary[];
|
||||
export async function getTenantLemonSqueezyTransactions(cursor?: string): Promise<{
|
||||
data: LemonSqueezyOrderSummary[];
|
||||
nextCursor: string | null;
|
||||
hasMore: boolean;
|
||||
}> {
|
||||
@@ -2745,8 +2745,8 @@ export async function getTenantPaddleTransactions(cursor?: string): Promise<{
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
console.error('[API] Failed to load Paddle transactions', response.status, payload);
|
||||
throw new Error('Failed to load Paddle transactions');
|
||||
console.error('[API] Failed to load Lemon Squeezy transactions', response.status, payload);
|
||||
throw new Error('Failed to load Lemon Squeezy transactions');
|
||||
}
|
||||
|
||||
const payload = await safeJson(response) ?? {};
|
||||
@@ -2754,17 +2754,17 @@ export async function getTenantPaddleTransactions(cursor?: string): Promise<{
|
||||
const meta = payload.meta ?? {};
|
||||
|
||||
return {
|
||||
data: entries.map(normalizePaddleTransaction),
|
||||
data: entries.map(normalizeLemonSqueezyOrder),
|
||||
nextCursor: typeof meta.next === 'string' ? meta.next : null,
|
||||
hasMore: Boolean(meta.has_more),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createTenantPaddleCheckout(
|
||||
export async function createTenantLemonSqueezyCheckout(
|
||||
packageId: number,
|
||||
urls?: { success_url?: string; return_url?: string }
|
||||
): Promise<{ checkout_url: string; id: string; expires_at?: string; checkout_session_id?: string }> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/packages/paddle-checkout', {
|
||||
const response = await authorizedFetch('/api/v1/tenant/packages/lemonsqueezy-checkout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -2796,15 +2796,15 @@ export async function createTenantBillingPortalSession(): Promise<{ url: string
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
console.error('[API] Failed to create Paddle portal session', response.status, payload);
|
||||
throw new Error('Failed to create Paddle portal session');
|
||||
console.error('[API] Failed to create Lemon Squeezy portal session', response.status, payload);
|
||||
throw new Error('Failed to create Lemon Squeezy portal session');
|
||||
}
|
||||
|
||||
const payload = await safeJson(response);
|
||||
const url = payload?.url;
|
||||
|
||||
if (typeof url !== 'string' || url.length === 0) {
|
||||
throw new Error('Paddle portal session missing URL');
|
||||
throw new Error('Lemon Squeezy portal session missing URL');
|
||||
}
|
||||
|
||||
return { url };
|
||||
@@ -2848,13 +2848,13 @@ export async function getTenantAddonHistory(page = 1, perPage = 25): Promise<{
|
||||
|
||||
export async function completeTenantPackagePurchase(params: {
|
||||
packageId: number;
|
||||
paddleTransactionId: string;
|
||||
orderId: string;
|
||||
}): Promise<void> {
|
||||
const { packageId, paddleTransactionId } = params;
|
||||
const { packageId, orderId } = params;
|
||||
const payload: Record<string, unknown> = { package_id: packageId };
|
||||
|
||||
if (paddleTransactionId) {
|
||||
payload.paddle_transaction_id = paddleTransactionId;
|
||||
if (orderId) {
|
||||
payload.lemonsqueezy_order_id = orderId;
|
||||
}
|
||||
|
||||
const response = await authorizedFetch('/api/v1/tenant/packages/complete', {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"actions": {
|
||||
"refresh": "Aktualisieren",
|
||||
"exportCsv": "Export als CSV",
|
||||
"portal": "Im Paddle-Portal verwalten",
|
||||
"portal": "Im Lemon Squeezy-Portal verwalten",
|
||||
"portalBusy": "Portal wird geöffnet...",
|
||||
"openPackages": "Pakete öffnen",
|
||||
"contactSupport": "Support kontaktieren"
|
||||
@@ -42,7 +42,7 @@
|
||||
"errors": {
|
||||
"load": "Paketdaten konnten nicht geladen werden.",
|
||||
"more": "Weitere Einträge konnten nicht geladen werden.",
|
||||
"portal": "Paddle-Portal konnte nicht geöffnet werden."
|
||||
"portal": "Lemon Squeezy-Portal konnte nicht geöffnet werden."
|
||||
},
|
||||
"checkoutSuccess": "Checkout abgeschlossen. Dein Paket wird in Kürze aktiviert.",
|
||||
"checkoutCancelled": "Checkout wurde abgebrochen.",
|
||||
@@ -62,8 +62,11 @@
|
||||
"checkoutActionBadge": "Aktion nötig",
|
||||
"checkoutActionButton": "Checkout fortsetzen",
|
||||
"checkoutFailureReasons": {
|
||||
"paddle_failed": "Die Zahlung wurde abgelehnt.",
|
||||
"paddle_cancelled": "Der Checkout wurde abgebrochen."
|
||||
"lemonsqueezy_failed": "Die Zahlung wurde abgelehnt.",
|
||||
"lemonsqueezy_cancelled": "Der Checkout wurde abgebrochen.",
|
||||
"lemonsqueezy_canceled": "Der Checkout wurde abgebrochen.",
|
||||
"lemonsqueezy_refunded": "Die Zahlung wurde erstattet.",
|
||||
"lemonsqueezy_voided": "Die Zahlung wurde storniert."
|
||||
},
|
||||
"sections": {
|
||||
"invoices": {
|
||||
@@ -125,9 +128,9 @@
|
||||
}
|
||||
},
|
||||
"transactions": {
|
||||
"title": "Paddle-Transaktionen",
|
||||
"description": "Neueste Paddle-Transaktionen für dieses Kundenkonto.",
|
||||
"empty": "Noch keine Paddle-Transaktionen.",
|
||||
"title": "Lemon Squeezy-Transaktionen",
|
||||
"description": "Neueste Lemon Squeezy-Transaktionen für dieses Kundenkonto.",
|
||||
"empty": "Noch keine Lemon Squeezy-Transaktionen.",
|
||||
"labels": {
|
||||
"transactionId": "Transaktion {{id}}",
|
||||
"checkoutId": "Checkout-ID: {{id}}",
|
||||
|
||||
@@ -192,25 +192,25 @@
|
||||
"failureTitle": "Aktivierung fehlgeschlagen",
|
||||
"errorMessage": "Kostenloses Paket konnte nicht aktiviert werden."
|
||||
},
|
||||
"paddle": {
|
||||
"sectionTitle": "Paddle",
|
||||
"heading": "Checkout mit Paddle",
|
||||
"genericError": "Der Paddle-Checkout konnte nicht geöffnet werden. Bitte versuche es erneut.",
|
||||
"errorTitle": "Paddle-Fehler",
|
||||
"processing": "Paddle-Checkout wird geöffnet …",
|
||||
"cta": "Paddle-Checkout öffnen",
|
||||
"hint": "Es öffnet sich ein neuer Tab über Paddle (Merchant of Record). Schließe dort die Zahlung ab und kehre anschließend zurück."
|
||||
"lemonsqueezy": {
|
||||
"sectionTitle": "Lemon Squeezy",
|
||||
"heading": "Checkout mit Lemon Squeezy",
|
||||
"genericError": "Der Lemon Squeezy-Checkout konnte nicht geöffnet werden. Bitte versuche es erneut.",
|
||||
"errorTitle": "Lemon Squeezy-Fehler",
|
||||
"processing": "Lemon Squeezy-Checkout wird geöffnet …",
|
||||
"cta": "Lemon Squeezy-Checkout öffnen",
|
||||
"hint": "Es öffnet sich ein neuer Tab über Lemon Squeezy (Merchant of Record). Schließe dort die Zahlung ab und kehre anschließend zurück."
|
||||
},
|
||||
"nextStepsTitle": "Nächste Schritte",
|
||||
"nextSteps": [
|
||||
"Optional: Abrechnung über Paddle im Billing-Bereich abschließen.",
|
||||
"Optional: Abrechnung über Lemon Squeezy im Billing-Bereich abschließen.",
|
||||
"Event-Setup durchlaufen und Fotoaufgaben, Team & Galerie konfigurieren.",
|
||||
"Vor dem Go-Live Event-Kontingent prüfen und Gäste-Link teilen."
|
||||
],
|
||||
"cta": {
|
||||
"billing": {
|
||||
"label": "Abrechnung starten",
|
||||
"description": "Öffnet den Billing-Bereich mit Paddle- und Kontingent-Optionen.",
|
||||
"description": "Öffnet den Billing-Bereich mit Lemon Squeezy- und Kontingent-Optionen.",
|
||||
"button": "Zu Billing & Zahlung"
|
||||
},
|
||||
"setup": {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"actions": {
|
||||
"refresh": "Refresh",
|
||||
"exportCsv": "Export CSV",
|
||||
"portal": "Manage in Paddle",
|
||||
"portal": "Manage in Lemon Squeezy",
|
||||
"portalBusy": "Opening portal...",
|
||||
"openPackages": "Open packages",
|
||||
"contactSupport": "Contact support"
|
||||
@@ -42,7 +42,7 @@
|
||||
"errors": {
|
||||
"load": "Unable to load package data.",
|
||||
"more": "Unable to load more entries.",
|
||||
"portal": "Unable to open the Paddle portal."
|
||||
"portal": "Unable to open the Lemon Squeezy portal."
|
||||
},
|
||||
"checkoutSuccess": "Checkout completed. Your package will activate shortly.",
|
||||
"checkoutCancelled": "Checkout was cancelled.",
|
||||
@@ -62,8 +62,11 @@
|
||||
"checkoutActionBadge": "Action needed",
|
||||
"checkoutActionButton": "Continue checkout",
|
||||
"checkoutFailureReasons": {
|
||||
"paddle_failed": "The payment was declined.",
|
||||
"paddle_cancelled": "The checkout was cancelled."
|
||||
"lemonsqueezy_failed": "The payment was declined.",
|
||||
"lemonsqueezy_cancelled": "The checkout was cancelled.",
|
||||
"lemonsqueezy_canceled": "The checkout was cancelled.",
|
||||
"lemonsqueezy_refunded": "The payment was refunded.",
|
||||
"lemonsqueezy_voided": "The payment was voided."
|
||||
},
|
||||
"sections": {
|
||||
"invoices": {
|
||||
@@ -125,9 +128,9 @@
|
||||
}
|
||||
},
|
||||
"transactions": {
|
||||
"title": "Paddle transactions",
|
||||
"description": "Recent Paddle transactions for this customer account.",
|
||||
"empty": "No Paddle transactions yet.",
|
||||
"title": "Lemon Squeezy transactions",
|
||||
"description": "Recent Lemon Squeezy transactions for this customer account.",
|
||||
"empty": "No Lemon Squeezy transactions yet.",
|
||||
"labels": {
|
||||
"transactionId": "Transaction {{id}}",
|
||||
"checkoutId": "Checkout ID: {{id}}",
|
||||
|
||||
@@ -192,25 +192,25 @@
|
||||
"failureTitle": "Activation failed",
|
||||
"errorMessage": "The free package could not be activated."
|
||||
},
|
||||
"paddle": {
|
||||
"sectionTitle": "Paddle",
|
||||
"heading": "Checkout with Paddle",
|
||||
"genericError": "The Paddle checkout could not be opened. Please try again.",
|
||||
"errorTitle": "Paddle error",
|
||||
"processing": "Opening the Paddle checkout …",
|
||||
"cta": "Open Paddle checkout",
|
||||
"hint": "A new tab opens via Paddle (merchant of record). Complete the payment there, then return to continue."
|
||||
"lemonsqueezy": {
|
||||
"sectionTitle": "Lemon Squeezy",
|
||||
"heading": "Checkout with Lemon Squeezy",
|
||||
"genericError": "The Lemon Squeezy checkout could not be opened. Please try again.",
|
||||
"errorTitle": "Lemon Squeezy error",
|
||||
"processing": "Opening the Lemon Squeezy checkout …",
|
||||
"cta": "Open Lemon Squeezy checkout",
|
||||
"hint": "A new tab opens via Lemon Squeezy (merchant of record). Complete the payment there, then return to continue."
|
||||
},
|
||||
"nextStepsTitle": "Next steps",
|
||||
"nextSteps": [
|
||||
"Optional: finish billing via Paddle inside the billing area.",
|
||||
"Optional: finish billing via Lemon Squeezy inside the billing area.",
|
||||
"Complete the event setup and configure photo tasks, team, and gallery.",
|
||||
"Check your event bundle before go-live and share your guest link."
|
||||
],
|
||||
"cta": {
|
||||
"billing": {
|
||||
"label": "Start billing",
|
||||
"description": "Opens the billing area with Paddle bundle options.",
|
||||
"description": "Opens the billing area with Lemon Squeezy bundle options.",
|
||||
"button": "Go to billing"
|
||||
},
|
||||
"setup": {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createRoot } from 'react-dom/client';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { TamaguiProvider, Theme } from '@tamagui/core';
|
||||
import { TamaguiProvider, Theme } from 'tamagui';
|
||||
import '@tamagui/core/reset.css';
|
||||
import tamaguiConfig from '../../../tamagui.config';
|
||||
import { AuthProvider } from './auth/context';
|
||||
|
||||
@@ -12,10 +12,10 @@ import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
import {
|
||||
createTenantBillingPortalSession,
|
||||
getTenantPackagesOverview,
|
||||
getTenantPaddleTransactions,
|
||||
getTenantLemonSqueezyTransactions,
|
||||
getTenantPackageCheckoutStatus,
|
||||
TenantPackageSummary,
|
||||
PaddleTransactionSummary,
|
||||
LemonSqueezyOrderSummary,
|
||||
} from '../api';
|
||||
import { TenantAddonHistoryEntry, getTenantAddonHistory } from '../api';
|
||||
import { getApiErrorMessage } from '../lib/apiError';
|
||||
@@ -52,7 +52,7 @@ export default function MobileBillingPage() {
|
||||
const { textStrong, text, muted, subtle, danger, border, primary, accentSoft } = useAdminTheme();
|
||||
const [packages, setPackages] = React.useState<TenantPackageSummary[]>([]);
|
||||
const [activePackage, setActivePackage] = React.useState<TenantPackageSummary | null>(null);
|
||||
const [transactions, setTransactions] = React.useState<PaddleTransactionSummary[]>([]);
|
||||
const [transactions, setTransactions] = React.useState<LemonSqueezyOrderSummary[]>([]);
|
||||
const [addons, setAddons] = React.useState<TenantAddonHistoryEntry[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
@@ -78,7 +78,7 @@ export default function MobileBillingPage() {
|
||||
try {
|
||||
const [pkg, trx, addonHistory] = await Promise.all([
|
||||
getTenantPackagesOverview({ force: true }),
|
||||
getTenantPaddleTransactions().catch(() => ({ data: [] as PaddleTransactionSummary[] })),
|
||||
getTenantLemonSqueezyTransactions().catch(() => ({ data: [] as LemonSqueezyOrderSummary[] })),
|
||||
getTenantAddonHistory().catch(() => ({ data: [] as TenantAddonHistoryEntry[] })),
|
||||
]);
|
||||
setPackages(pkg.packages ?? []);
|
||||
@@ -116,7 +116,7 @@ export default function MobileBillingPage() {
|
||||
window.open(url, '_blank', 'noopener');
|
||||
}
|
||||
} catch (err) {
|
||||
const message = getApiErrorMessage(err, t('billing.errors.portal', 'Konnte das Paddle-Portal nicht öffnen.'));
|
||||
const message = getApiErrorMessage(err, t('billing.errors.portal', 'Konnte das Lemon Squeezy-Portal nicht öffnen.'));
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setPortalBusy(false);
|
||||
@@ -388,7 +388,7 @@ export default function MobileBillingPage() {
|
||||
{t('billing.sections.packages.hint', 'Active package, limits, and history at a glance.')}
|
||||
</Text>
|
||||
<CTAButton
|
||||
label={portalBusy ? t('billing.actions.portalBusy', 'Öffne Portal...') : t('billing.actions.portal', 'Manage in Paddle')}
|
||||
label={portalBusy ? t('billing.actions.portalBusy', 'Öffne Portal...') : t('billing.actions.portal', 'Manage in Lemon Squeezy')}
|
||||
onPress={openPortal}
|
||||
disabled={portalBusy}
|
||||
/>
|
||||
|
||||
@@ -1465,7 +1465,7 @@ function WatermarkPreview({
|
||||
background: ADMIN_GRADIENTS.softCard,
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', inset: 0, background: `linear-gradient(180deg, ${overlay}, transparent)` }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: `linear-gradient(180deg, rgba(0,0,0,0.4), transparent)` }} />
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -222,7 +222,12 @@ export default function MobileDashboardPage() {
|
||||
return (
|
||||
<MobileShell activeTab="home" title={t('mobileDashboard.title', 'Dashboard')}>
|
||||
<DashboardCard padding="$0">
|
||||
<YStack padding="$3" gap="$2">
|
||||
<YStack
|
||||
padding="$3"
|
||||
gap="$2"
|
||||
animation="bouncy"
|
||||
enterStyle={{ opacity: 0, scale: 0.9 }}
|
||||
>
|
||||
<SectionHeader
|
||||
title={t('dashboard:overview.title', 'At a glance')}
|
||||
showSeparator={false}
|
||||
|
||||
@@ -224,7 +224,7 @@ function PackageShopCard({
|
||||
const isResellerCatalog = catalogType === 'reseller';
|
||||
const statusLabel = getPackageStatusLabel({ t, isActive, owned });
|
||||
const isSubdued = Boolean(!isResellerCatalog && (isDowngrade || !isUpgrade) && !isActive);
|
||||
const canSelect = isResellerCatalog ? Boolean(pkg.paddle_price_id) : canSelectPackage(isUpgrade, isActive);
|
||||
const canSelect = isResellerCatalog ? Boolean(pkg.lemonsqueezy_variant_id) : canSelectPackage(isUpgrade, isActive);
|
||||
const hasManageAction = Boolean(isActive && onManage);
|
||||
const includedTierLabel = resolveIncludedTierLabel(t, pkg.included_package_slug ?? null);
|
||||
const handlePress = isActive ? onManage : canSelect ? onSelect : undefined;
|
||||
@@ -524,7 +524,7 @@ function PackageShopCompareView({
|
||||
<YStack width={labelWidth} />
|
||||
{entries.map((entry) => {
|
||||
const isResellerCatalog = catalogType === 'reseller';
|
||||
const canSelect = isResellerCatalog ? Boolean(entry.pkg.paddle_price_id) : canSelectPackage(entry.isUpgrade, entry.isActive);
|
||||
const canSelect = isResellerCatalog ? Boolean(entry.pkg.lemonsqueezy_variant_id) : canSelectPackage(entry.isUpgrade, entry.isActive);
|
||||
const label = isResellerCatalog
|
||||
? canSelect
|
||||
? t('shop.partner.buy', 'Kaufen')
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { ChevronLeft, Bell, QrCode, ChevronsUpDown, Search } from 'lucide-react';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { YStack, XStack, SizableText as Text, Image } from 'tamagui';
|
||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||
import { Image } from '@tamagui/image';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEventContext } from '../../context/EventContext';
|
||||
import { BottomNav, NavKey } from './BottomNav';
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Card } from '@tamagui/card';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Card, YStack, XStack, SizableText as Text, Tabs, Separator } from 'tamagui';
|
||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||
import { Tabs, Separator } from 'tamagui';
|
||||
import { useAdminTheme } from '../theme';
|
||||
import { withAlpha } from './colors';
|
||||
|
||||
@@ -16,7 +13,9 @@ export function MobileCard({
|
||||
const { surface, border, shadow, glassSurface, glassBorder, glassShadow } = useAdminTheme();
|
||||
return (
|
||||
<YStack
|
||||
className={['admin-fade-up', className].filter(Boolean).join(' ')}
|
||||
className={className}
|
||||
animation="bouncy"
|
||||
enterStyle={{ opacity: 0, y: 10, scale: 0.9 }}
|
||||
backgroundColor={glassSurface ?? surface}
|
||||
borderRadius={20}
|
||||
borderWidth={2}
|
||||
@@ -131,6 +130,8 @@ export function CTAButton({
|
||||
}}
|
||||
>
|
||||
<XStack
|
||||
animation="quick"
|
||||
pressStyle={{ scale: 0.97, opacity: 0.9 }}
|
||||
height={52}
|
||||
borderRadius={18}
|
||||
alignItems="center"
|
||||
@@ -335,7 +336,8 @@ export function ActionTile({
|
||||
disabled={disabled}
|
||||
>
|
||||
<YStack
|
||||
className="admin-fade-up"
|
||||
animation="lazy"
|
||||
pressStyle={{ scale: 0.96, opacity: 0.8 }}
|
||||
style={tileStyle}
|
||||
borderRadius={isCluster ? 14 : 16}
|
||||
padding="$3"
|
||||
|
||||
@@ -36,9 +36,6 @@ export function MobileSheet({
|
||||
const { surface, textStrong, muted, overlay, shadow, border } = useAdminTheme();
|
||||
const bottomOffset = `max(env(safe-area-inset-bottom, 0px), ${bottomOffsetPx}px)`;
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Sheet
|
||||
modal
|
||||
@@ -53,29 +50,33 @@ export function MobileSheet({
|
||||
dismissOnOverlayPress
|
||||
dismissOnSnapToBottom
|
||||
zIndex={100000}
|
||||
animation="lazy"
|
||||
>
|
||||
<Sheet.Overlay {...({ backgroundColor: `${overlay}66` } as any)} />
|
||||
<Sheet.Overlay
|
||||
animation="lazy"
|
||||
enterStyle={{ opacity: 0 }}
|
||||
exitStyle={{ opacity: 0 }}
|
||||
backgroundColor={overlay}
|
||||
/>
|
||||
<Sheet.Frame
|
||||
{...({
|
||||
width: '100%',
|
||||
maxWidth: 520,
|
||||
alignSelf: 'center',
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
backgroundColor: surface,
|
||||
padding,
|
||||
paddingBottom,
|
||||
shadowColor: shadow,
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 18,
|
||||
shadowOffset: { width: 0, height: -8 },
|
||||
} as any)}
|
||||
width="100%"
|
||||
maxWidth={520}
|
||||
alignSelf="center"
|
||||
borderTopLeftRadius={24}
|
||||
borderTopRightRadius={24}
|
||||
backgroundColor={surface}
|
||||
padding={padding}
|
||||
paddingBottom={paddingBottom}
|
||||
shadowColor={shadow}
|
||||
shadowOpacity={0.12}
|
||||
shadowRadius={18}
|
||||
shadowOffset={{ width: 0, height: -8 }}
|
||||
style={{ marginBottom: bottomOffset }}
|
||||
>
|
||||
<Sheet.Handle height={5} width={48} backgroundColor={border} borderRadius={999} marginBottom="$3" />
|
||||
<Sheet.ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
{...({ contentContainerStyle: { paddingBottom: 6 } } as any)}
|
||||
contentContainerStyle={{ paddingBottom: 6 }}
|
||||
>
|
||||
<YStack gap={contentSpacing}>
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import { createTenantPaddleCheckout } from '../../api';
|
||||
import { createTenantLemonSqueezyCheckout } from '../../api';
|
||||
import { adminPath } from '../../constants';
|
||||
import { getApiErrorMessage } from '../../lib/apiError';
|
||||
import { storePendingCheckout } from '../lib/billingCheckout';
|
||||
@@ -33,7 +33,7 @@ export function usePackageCheckout(): {
|
||||
cancelUrl.searchParams.set('checkout', 'cancel');
|
||||
cancelUrl.searchParams.set('package_id', String(packageId));
|
||||
|
||||
const { checkout_url, checkout_session_id } = await createTenantPaddleCheckout(packageId, {
|
||||
const { checkout_url, checkout_session_id } = await createTenantLemonSqueezyCheckout(packageId, {
|
||||
success_url: successUrl.toString(),
|
||||
return_url: cancelUrl.toString(),
|
||||
});
|
||||
|
||||
@@ -161,7 +161,7 @@ export function useAdminTheme() {
|
||||
infoText: String(theme.blue10?.val ?? ADMIN_COLORS.primaryStrong),
|
||||
danger: String(theme.danger?.val ?? ADMIN_COLORS.danger),
|
||||
backdrop: String(theme.backgroundStrong?.val ?? ADMIN_COLORS.backdrop),
|
||||
overlay: withAlpha(String(theme.backgroundStrong?.val ?? ADMIN_COLORS.backdrop), 0.6),
|
||||
overlay: withAlpha(ADMIN_COLORS.backdrop, 0.7),
|
||||
shadow: String(theme.shadowColor?.val ?? 'rgba(15, 23, 42, 0.08)'),
|
||||
glassSurface,
|
||||
glassSurfaceStrong,
|
||||
|
||||
@@ -166,7 +166,7 @@ export default function WelcomeSummaryPage() {
|
||||
{(t('summary.nextSteps', {
|
||||
returnObjects: true,
|
||||
defaultValue: [
|
||||
'Optional: finish billing via Paddle inside the billing area.',
|
||||
'Optional: finish billing via Lemon Squeezy inside the billing area.',
|
||||
'Complete the event setup and configure tasks, team, and gallery.',
|
||||
'Check your event slots before go-live and share your guest link.',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user