Compare commits
2 Commits
ee3e9737c4
...
d5d53b563c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5d53b563c | ||
|
|
c4fa0fc06e |
@@ -140,7 +140,7 @@
|
||||
{"id":"fotospiel-app-xg5","title":"Live Show: Admin app moderation queue UI","acceptance_criteria":"- Dedicated Live Show moderation API endpoints exist for list + approve/reject/clear\\n- Admin mobile UI exposes Live Show queue with status filter and actions\\n- PhotoResource includes live_* fields for admin UI\\n- Feature tests cover list + approve/reject/clear workflows","notes":"Added dedicated Live Show moderation API (tenant admin): /events/{slug}/live-show/photos + approve/reject/clear actions. Added LiveShowPhotoController + FormRequests. PhotoResource now exposes live_* fields. Admin app: new Live Show queue page, route, and Event detail shortcut tile. Admin API updated with Live Show functions + types. Added translations (EN/DE) for Live Show queue UI. Tests: tests/Feature/LiveShowPhotoControllerTest.php.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-01-05T11:11:15.006484132+01:00","created_by":"soeren","updated_at":"2026-01-05T14:03:41.410176482+01:00","closed_at":"2026-01-05T14:03:41.410176482+01:00","close_reason":"Closed","dependencies":[{"issue_id":"fotospiel-app-xg5","depends_on_id":"fotospiel-app-t1k","type":"blocks","created_at":"2026-01-05T11:12:38.94145573+01:00","created_by":"soeren"}]}
|
||||
{"id":"fotospiel-app-xht","title":"Paddle migration: tenant ↔ Paddle customer sync + webhook handlers","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:58:01.028435913+01:00","created_by":"soeren","updated_at":"2026-01-01T15:58:06.685122343+01:00","closed_at":"2026-01-01T15:58:06.685122343+01:00","close_reason":"Completed in codebase (verified)"}
|
||||
{"id":"fotospiel-app-y1f","title":"Compliance tools: superadmin data export + retention override UI","description":"Add superadmin compliance tools for data exports and retention overrides.\nScope: list export requests, status, expiry, and allow manual retry/cancel; add per-tenant/event retention override UI with audit logging.\nEnsure access is restricted to superadmins and no PII is exposed beyond existing export metadata.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T17:34:29.825347299+01:00","created_by":"soeren","updated_at":"2026-01-02T22:49:53.586758621+01:00","closed_at":"2026-01-02T22:49:53.586758621+01:00","close_reason":"Closed"}
|
||||
{"id":"fotospiel-app-yii","title":"Implement 'Upgrade to Premium' flow for Analytics Upsell","description":"The Analytics page currently has an upsell screen for non-premium users. The 'Upgrade to Premium' button redirects to the billing page, but the actual upgrade/purchase flow needs to be fully implemented and verified to allow users to unlock the feature.","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-06T16:13:55.446495378+01:00","created_by":"soeren","updated_at":"2026-01-06T16:13:55.446495378+01:00"}
|
||||
{"id":"fotospiel-app-yii","title":"Implement 'Upgrade to Premium' flow for Analytics Upsell","description":"The Analytics page currently has an upsell screen for non-premium users. The 'Upgrade to Premium' button redirects to the billing page, but the actual upgrade/purchase flow needs to be fully implemented and verified to allow users to unlock the feature.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-06T16:13:55.446495378+01:00","created_by":"soeren","updated_at":"2026-01-06T16:35:41.968964977+01:00","closed_at":"2026-01-06T16:35:41.968970147+01:00"}
|
||||
{"id":"fotospiel-app-z2k","title":"Ops health widget visual polish","description":"Replace Tailwind utility styling in ops health widget with Filament components and icon-driven layout.","notes":"Updated queue health widget layout to use Filament cards, badges, empty states, and grid utilities; added status strip and alert rail.","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-01T21:34:39.851728527+01:00","created_by":"soeren","updated_at":"2026-01-01T21:34:59.834597413+01:00","closed_at":"2026-01-01T21:34:59.834597413+01:00","close_reason":"completed"}
|
||||
{"id":"fotospiel-app-z5g","title":"Tenant admin onboarding: PWA/Capacitor/TWA packaging prep","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-01T16:08:46.126417696+01:00","created_by":"soeren","updated_at":"2026-01-01T16:08:46.126417696+01:00"}
|
||||
{"id":"fotospiel-app-zli","title":"SEC-FE-01 CSP nonce/hashing rollout","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-01T15:55:03.625388684+01:00","created_by":"soeren","updated_at":"2026-01-01T15:55:09.286391766+01:00","closed_at":"2026-01-01T15:55:09.286391766+01:00","close_reason":"Completed in codebase (verified)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
fotospiel-app-83q
|
||||
fotospiel-app-yii
|
||||
|
||||
@@ -2454,6 +2454,25 @@ export async function getTenantPaddleTransactions(cursor?: string): Promise<{
|
||||
};
|
||||
}
|
||||
|
||||
export async function createTenantPaddleCheckout(
|
||||
packageId: number,
|
||||
urls?: { success_url?: string; return_url?: string }
|
||||
): Promise<{ checkout_url: string; id: string; expires_at?: string }> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/packages/paddle-checkout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
package_id: packageId,
|
||||
success_url: urls?.success_url,
|
||||
return_url: urls?.return_url,
|
||||
}),
|
||||
});
|
||||
return await jsonOrThrow<{ checkout_url: string; id: string; expires_at?: string }>(
|
||||
response,
|
||||
'Failed to create checkout'
|
||||
);
|
||||
}
|
||||
|
||||
export async function createTenantBillingPortalSession(): Promise<{ url: string }> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/billing/portal', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getTenantPaddleTransactions,
|
||||
TenantPackageSummary,
|
||||
PaddleTransactionSummary,
|
||||
createTenantPaddleCheckout,
|
||||
} from '../api';
|
||||
import { TenantAddonHistoryEntry, getTenantAddonHistory } from '../api';
|
||||
import { getApiErrorMessage } from '../lib/apiError';
|
||||
@@ -40,6 +41,7 @@ export default function MobileBillingPage() {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [portalBusy, setPortalBusy] = React.useState(false);
|
||||
const [upgradeBusy, setUpgradeBusy] = React.useState<number | null>(null);
|
||||
const packagesRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const invoicesRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const supportEmail = 'support@fotospiel.de';
|
||||
@@ -95,6 +97,26 @@ export default function MobileBillingPage() {
|
||||
}
|
||||
}, [portalBusy, t]);
|
||||
|
||||
const handleUpgrade = React.useCallback(async (pkg: TenantPackageSummary) => {
|
||||
if (upgradeBusy) return;
|
||||
setUpgradeBusy(pkg.package_id);
|
||||
|
||||
try {
|
||||
const { checkout_url } = await createTenantPaddleCheckout(pkg.package_id, {
|
||||
success_url: window.location.href,
|
||||
return_url: window.location.href,
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = checkout_url;
|
||||
}
|
||||
} catch (err) {
|
||||
const message = getApiErrorMessage(err, t('billing.errors.upgrade', 'Konnte Checkout nicht starten.'));
|
||||
toast.error(message);
|
||||
setUpgradeBusy(null);
|
||||
}
|
||||
}, [upgradeBusy, t]);
|
||||
|
||||
React.useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
@@ -161,7 +183,12 @@ export default function MobileBillingPage() {
|
||||
{packages
|
||||
.filter((pkg) => !activePackage || pkg.id !== activePackage.id)
|
||||
.map((pkg) => (
|
||||
<PackageCard key={pkg.id} pkg={pkg} />
|
||||
<PackageCard
|
||||
key={pkg.id}
|
||||
pkg={pkg}
|
||||
onUpgrade={() => handleUpgrade(pkg)}
|
||||
upgradeBusy={upgradeBusy === pkg.package_id}
|
||||
/>
|
||||
))}
|
||||
</YStack>
|
||||
)}
|
||||
@@ -265,12 +292,16 @@ function PackageCard({
|
||||
isActive = false,
|
||||
onOpenPortal,
|
||||
portalBusy,
|
||||
onUpgrade,
|
||||
upgradeBusy,
|
||||
}: {
|
||||
pkg: TenantPackageSummary;
|
||||
label?: string;
|
||||
isActive?: boolean;
|
||||
onOpenPortal?: () => void;
|
||||
portalBusy?: boolean;
|
||||
onUpgrade?: () => void;
|
||||
upgradeBusy?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation('management');
|
||||
const { border, primary, accentSoft, textStrong, muted } = useAdminTheme();
|
||||
@@ -383,6 +414,14 @@ function PackageCard({
|
||||
tone={isDanger ? 'danger' : 'primary'}
|
||||
/>
|
||||
) : null}
|
||||
{!isActive && onUpgrade ? (
|
||||
<CTAButton
|
||||
label={upgradeBusy ? t('billing.actions.upgradeBusy', 'Einen Moment...') : t('billing.actions.upgrade', 'Buy / Upgrade')}
|
||||
onPress={onUpgrade}
|
||||
disabled={upgradeBusy}
|
||||
tone="primary"
|
||||
/>
|
||||
) : null}
|
||||
</MobileCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function MobileEventAnalyticsPage() {
|
||||
</YStack>
|
||||
<CTAButton
|
||||
label={t('analytics.upgradeAction', 'Upgrade to Premium')}
|
||||
onPress={() => navigate(adminPath('/mobile/billing'))}
|
||||
onPress={() => navigate(adminPath('/mobile/billing#packages'))}
|
||||
/>
|
||||
</MobileCard>
|
||||
</MobileShell>
|
||||
|
||||
Reference in New Issue
Block a user