fix(odeme): action redirect fırlatmaz, hedef döndürür + çift paket çekimine sunucu koruması
All checks were successful
Deploy / deploy (push) Successful in 8m46s
All checks were successful
Deploy / deploy (push) Successful in 8m46s
- baslatOdeme { error } | { yonlendir } döndürür; SatinAlForm client'ta
router.push yapar (cacheComponents altında geri dönüşte buton tıklamaya
sağır kalıyordu); tıklamada ok → dönen halka
- paketi olan yeniden 'paket', paketi olmayan 'topup' başlatamaz
- /odeme, paket alınmışken eski bekleyen paket siparişinin formunu göstermez
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState, useEffect } from "react";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { useLayoutEffect, useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ArrowRight, Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import { baslatOdeme, type OdemeBaslatDurum } from "../odeme-actions";
|
||||
import { baslatOdeme } from "../odeme-actions";
|
||||
|
||||
const BAGLANTI_HATASI =
|
||||
"Ödeme başlatılamadı; kartından çekim yapılmadı. Bağlantını kontrol edip tekrar dener misin?";
|
||||
|
||||
export function SatinAlForm({
|
||||
urun,
|
||||
@@ -16,24 +20,50 @@ export function SatinAlForm({
|
||||
label: string;
|
||||
vurgulu?: boolean;
|
||||
}) {
|
||||
const [state, formAction, pending] = useActionState<
|
||||
OdemeBaslatDurum,
|
||||
FormData
|
||||
>(baslatOdeme, undefined);
|
||||
const router = useRouter();
|
||||
const [pending, startTransition] = useTransition();
|
||||
// Action bitti, /odeme'ye geçiş sürüyor: buton o arada yeniden açılıp ikinci
|
||||
// bir sipariş başlatmasın.
|
||||
const [gidiyor, setGidiyor] = useState(false);
|
||||
const mesgul = pending || gidiyor;
|
||||
|
||||
useEffect(() => {
|
||||
if (state?.error) toast.error(state.error);
|
||||
}, [state]);
|
||||
// cacheComponents açıkken /paket, /odeme'ye geçince unmount olmaz; gizli
|
||||
// <Activity> içinde state'iyle saklanır. Geri dönen kullanıcı kilitli değil
|
||||
// taze bir buton bulsun diye sayfa gizlenirken sıfırlanır (layout effect
|
||||
// cleanup'ı Activity gizlenince çalışır).
|
||||
useLayoutEffect(() => {
|
||||
return () => setGidiyor(false);
|
||||
}, []);
|
||||
|
||||
// Yönlendirme client'ta: action `redirect()` fırlatmaz, hedefi döndürür
|
||||
// (nedeni odeme-actions.ts'te). Hata toast'ı da burada, effect'te değil —
|
||||
// effect'ler Activity geri açılınca yeniden koştuğu için eski hatayı
|
||||
// tekrar gösteriyordu.
|
||||
function gonder() {
|
||||
olay("odeme_baslatildi", { urun });
|
||||
startTransition(async () => {
|
||||
let sonuc;
|
||||
try {
|
||||
sonuc = await baslatOdeme(urun);
|
||||
} catch {
|
||||
toast.error(BAGLANTI_HATASI);
|
||||
return;
|
||||
}
|
||||
if ("error" in sonuc) {
|
||||
toast.error(sonuc.error);
|
||||
return;
|
||||
}
|
||||
setGidiyor(true);
|
||||
router.push(sonuc.yonlendir);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
action={formAction}
|
||||
onSubmit={() => olay("odeme_baslatildi", { urun })}
|
||||
>
|
||||
<input type="hidden" name="urun" value={urun} />
|
||||
<form action={gonder}>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={pending}
|
||||
disabled={mesgul}
|
||||
aria-busy={mesgul}
|
||||
className={
|
||||
vurgulu
|
||||
? "h-12 w-full cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
@@ -41,11 +71,15 @@ export function SatinAlForm({
|
||||
}
|
||||
variant={vurgulu ? "default" : "outline"}
|
||||
>
|
||||
{/* Tıklamada buton içeriği değişmez (metin/spinner takası yok); dar
|
||||
top-up kartında taşmaya yol açıyordu. Çift gönderim `disabled`
|
||||
ile engellenir. */}
|
||||
{/* Tıklamada metin değişmez (dar top-up kartında taşıyordu); geri
|
||||
bildirim aynı boyuttaki ikon takasıyla verilir: ok → dönen halka.
|
||||
Çift gönderim `disabled` ile engellenir. */}
|
||||
{label}
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
{mesgul ? (
|
||||
<Loader2 className="size-4 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user