feat(billing): useStripeCheckout hook + post-redirect upgrade success
- useStripeCheckout: mutation + redirect full-page, pendingPriceType exposed - PricingPage migré vers useStripeCheckout (suppression useMutation inline) - useUpgradeSuccessHandler: détecte ?upgrade=success, invalide plan cache, clean URL - UpgradeSuccessBanner: callout success dans DashboardPage - Tests: 203 → 212 verts (+9) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9edfbb3c95
commit
bda7feb196
7 changed files with 371 additions and 25 deletions
|
|
@ -17,9 +17,9 @@
|
|||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { usePlan } from '@/features/dashboard/hooks/usePlan'
|
||||
import { createCheckoutSession, type PriceType } from '../api'
|
||||
import { type PriceType } from '../api'
|
||||
import { useStripeCheckout } from '../hooks/useStripeCheckout'
|
||||
import { PlanCard, type PlanCardCta } from '../components/PlanCard'
|
||||
|
||||
type Plan = 'free' | 'standard' | 'premium'
|
||||
|
|
@ -148,34 +148,21 @@ function buildCtaConfigs(
|
|||
|
||||
export function PricingPage() {
|
||||
const { data: planData, isLoading } = usePlan()
|
||||
const [pendingType, setPendingType] = useState<PriceType | null>(null)
|
||||
const [errorByType, setErrorByType] = useState<Partial<Record<PriceType, string>>>({})
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: createCheckoutSession,
|
||||
onSuccess: (data) => {
|
||||
// Redirection full-page vers Stripe Checkout. L'utilisateur reviendra
|
||||
// sur /dashboard?upgrade=success après paiement (cf. backend success_url).
|
||||
window.location.href = data.url
|
||||
},
|
||||
onError: (err: Error, priceType) => {
|
||||
setErrorByType((prev) => ({
|
||||
...prev,
|
||||
[priceType]:
|
||||
err.message || 'Impossible de démarrer le paiement. Réessayez dans quelques instants.',
|
||||
}))
|
||||
setPendingType(null)
|
||||
},
|
||||
})
|
||||
const { checkout, pendingPriceType, error } = useStripeCheckout()
|
||||
// Mémorise le dernier priceType cliqué pour rattacher l'erreur globale du
|
||||
// hook à la bonne carte. Sprint 5c — `useStripeCheckout` n'expose qu'un
|
||||
// `error` global (l'utilisateur ne clique qu'un CTA à la fois).
|
||||
const [lastClicked, setLastClicked] = useState<PriceType | null>(null)
|
||||
|
||||
function handleUpgrade(priceType: PriceType) {
|
||||
setErrorByType((prev) => ({ ...prev, [priceType]: undefined }))
|
||||
setPendingType(priceType)
|
||||
mutation.mutate(priceType)
|
||||
setLastClicked(priceType)
|
||||
checkout(priceType)
|
||||
}
|
||||
|
||||
const plan = (planData?.plan as Plan | undefined) ?? 'free'
|
||||
const ctaConfigs = buildCtaConfigs(plan, pendingType, handleUpgrade)
|
||||
const ctaConfigs = buildCtaConfigs(plan, pendingPriceType, handleUpgrade)
|
||||
const errorByType: Partial<Record<PriceType, string>> =
|
||||
error && lastClicked ? { [lastClicked]: error } : {}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[1100px] px-5 py-6 lg:px-9 lg:py-9">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue