From 2cecec733ea4b3187c49f54c6a04d6e27a52b2b6 Mon Sep 17 00:00:00 2001 From: nico Date: Thu, 27 Nov 2025 14:46:49 +0800 Subject: [PATCH] Tambah cookies di bagian verifikasi, agar kedeteksi user sudah regis apa belom --- .../(dashboard)/auth/login-admin/page.tsx | 11 +- .../(dashboard)/auth/validasi-admin/page.tsx | 128 +++++++++++------- src/app/api/auth/_lib/api_fetch_auth.ts | 2 + src/app/api/auth/clear-flow/route.ts | 19 +++ src/app/api/auth/get-flow/route.ts | 22 +++ src/app/api/auth/login/route.ts | 40 +++++- src/app/api/auth/register/route.ts | 15 +- src/app/api/auth/set-flow/route.ts | 34 +++++ 8 files changed, 209 insertions(+), 62 deletions(-) create mode 100644 src/app/api/auth/clear-flow/route.ts create mode 100644 src/app/api/auth/get-flow/route.ts create mode 100644 src/app/api/auth/set-flow/route.ts diff --git a/src/app/admin/(dashboard)/auth/login-admin/page.tsx b/src/app/admin/(dashboard)/auth/login-admin/page.tsx index a44f35b0..3457cf45 100644 --- a/src/app/admin/(dashboard)/auth/login-admin/page.tsx +++ b/src/app/admin/(dashboard)/auth/login-admin/page.tsx @@ -15,7 +15,9 @@ function Login() { // Login.tsx async function onLogin() { + const cleanPhone = phone.replace(/\D/g, ''); + console.log(cleanPhone); if (cleanPhone.length < 10) { toast.error('Nomor telepon tidak valid'); return; @@ -25,6 +27,8 @@ function Login() { setLoading(true); const response = await apiFetchLogin({ nomor: cleanPhone }); + console.log(response); + if (!response.success) { toast.error(response.message || 'Gagal memproses login'); return; @@ -32,11 +36,12 @@ function Login() { // Simpan nomor untuk register localStorage.setItem('auth_nomor', cleanPhone); - if (response.isRegistered) { - // ✅ User lama: simpan kodeId & ke validasi + // ✅ User lama: simpan kodeId localStorage.setItem('auth_kodeId', response.kodeId); - router.push('/validasi'); + + // ✅ Cookie sudah di-set oleh API, langsung redirect + router.push('/validasi'); // Clean URL } else { // ❌ User baru: langsung ke registrasi (tanpa kodeId) router.push('/registrasi'); diff --git a/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx b/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx index 0ed24cd3..60871f00 100644 --- a/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx +++ b/src/app/admin/(dashboard)/auth/validasi-admin/page.tsx @@ -19,17 +19,34 @@ import { authStore } from '@/store/authStore'; export default function Validasi() { const router = useRouter(); + const [nomor, setNomor] = useState(null); const [otp, setOtp] = useState(''); const [loading, setLoading] = useState(false); const [isLoading, setIsLoading] = useState(true); const [kodeId, setKodeId] = useState(null); - const [isRegistrationFlow, setIsRegistrationFlow] = useState(false); // Tambahkan flag + const [isRegistrationFlow, setIsRegistrationFlow] = useState(false); - // Cek apakah ini alur registrasi + // ✅ Deteksi flow dari cookie via API useEffect(() => { - const storedUsername = localStorage.getItem('auth_username'); - setIsRegistrationFlow(!!storedUsername); + const checkFlow = async () => { + try { + const res = await fetch('/api/auth/get-flow', { + credentials: 'include' + }); + const data = await res.json(); + + if (data.success) { + setIsRegistrationFlow(data.flow === 'register'); + console.log('🔍 Flow detected from cookie:', data.flow); + } + } catch (error) { + console.error('❌ Error getting flow:', error); + setIsRegistrationFlow(false); + } + }; + + checkFlow(); }, []); useEffect(() => { @@ -68,10 +85,8 @@ export default function Validasi() { setLoading(true); try { if (isRegistrationFlow) { - // 🔑 Alur REGISTRASI await handleRegistrationVerification(); } else { - // 🔑 Alur LOGIN await handleLoginVerification(); } } catch (error) { @@ -82,55 +97,55 @@ export default function Validasi() { } }; -const handleRegistrationVerification = async () => { - const username = localStorage.getItem('auth_username'); - if (!username) { - toast.error('Data registrasi tidak ditemukan.'); - return; - } + const handleRegistrationVerification = async () => { + const username = localStorage.getItem('auth_username'); + if (!username) { + toast.error('Data registrasi tidak ditemukan.'); + return; + } - const cleanNomor = nomor?.replace(/\D/g, '') ?? ''; - if (cleanNomor.length < 10 || username.trim().length < 5) { - toast.error('Data tidak valid'); - return; - } + const cleanNomor = nomor?.replace(/\D/g, '') ?? ''; + if (cleanNomor.length < 10 || username.trim().length < 5) { + toast.error('Data tidak valid'); + return; + } - // Verifikasi OTP dulu - const verifyRes = await fetch('/api/auth/verify-otp-register', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ nomor: cleanNomor, otp, kodeId }), - }); + const verifyRes = await fetch('/api/auth/verify-otp-register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ nomor: cleanNomor, otp, kodeId }), + }); - const verifyData = await verifyRes.json(); - if (!verifyRes.ok) { - toast.error(verifyData.message || 'Verifikasi OTP gagal'); - return; - } + const verifyData = await verifyRes.json(); + if (!verifyRes.ok) { + toast.error(verifyData.message || 'Verifikasi OTP gagal'); + return; + } - // ✅ Kirim ke finalize-registration → akan redirect ke /waiting-room - const finalizeRes = await fetch('/api/auth/finalize-registration', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ nomor, username, kodeId }), - credentials: 'include' - }); + const finalizeRes = await fetch('/api/auth/finalize-registration', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ nomor, username, kodeId }), + credentials: 'include' + }); - if (finalizeRes.redirected) { - // ✅ Redirect otomatis oleh server - window.location.href = finalizeRes.url; - } else { const data = await finalizeRes.json(); - toast.error(data.message || 'Registrasi gagal'); - } -}; + + if (data.success || finalizeRes.redirected) { + // ✅ Cleanup setelah registrasi sukses + await cleanupStorage(); + window.location.href = '/waiting-room'; + } else { + toast.error(data.message || 'Registrasi gagal'); + } + }; - // ✅ Verifikasi OTP untuk LOGIN const handleLoginVerification = async () => { const loginRes = await fetch('/api/auth/verify-otp-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nomor, otp, kodeId }), + credentials: 'include' }); const loginData = await loginRes.json(); @@ -148,7 +163,8 @@ const handleRegistrationVerification = async () => { roleId: Number(roleId), }); - cleanupStorage(); + // ✅ Cleanup setelah login sukses + await cleanupStorage(); if (!isActive) { window.location.href = '/waiting-room'; @@ -161,23 +177,35 @@ const handleRegistrationVerification = async () => { const getRedirectPath = (roleId: number): string => { switch (roleId) { - case 0: // DEVELOPER - case 1: // SUPERADMIN - case 2: // ADMIN_DESA + case 0: + case 1: + case 2: return '/admin/landing-page/profil/program-inovasi'; - case 3: // ADMIN_KESEHATAN + case 3: return '/admin/kesehatan/posyandu'; - case 4: // ADMIN_PENDIDIKAN + case 4: return '/admin/pendidikan/info-sekolah/jenjang-pendidikan'; default: return '/admin'; } }; - const cleanupStorage = () => { + // ✅ CLEANUP FUNCTION - Hapus localStorage + Cookie + const cleanupStorage = async () => { + // Clear localStorage localStorage.removeItem('auth_kodeId'); localStorage.removeItem('auth_nomor'); localStorage.removeItem('auth_username'); + + // Clear cookie + try { + await fetch('/api/auth/clear-flow', { + method: 'POST', + credentials: 'include' + }); + } catch (error) { + console.error('Error clearing flow cookie:', error); + } }; const handleResend = async () => { diff --git a/src/app/api/auth/_lib/api_fetch_auth.ts b/src/app/api/auth/_lib/api_fetch_auth.ts index 19de8fc1..e57486e4 100644 --- a/src/app/api/auth/_lib/api_fetch_auth.ts +++ b/src/app/api/auth/_lib/api_fetch_auth.ts @@ -14,6 +14,7 @@ export const apiFetchLogin = async ({ nomor }: { nomor: string }) => { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ nomor: cleanPhone }), + credentials: 'include' }); // Pastikan respons bisa di-parse sebagai JSON @@ -58,6 +59,7 @@ export const apiFetchRegister = async ({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: username.trim(), nomor: cleanPhone }), + credentials: 'include', }); const data = await response.json(); diff --git a/src/app/api/auth/clear-flow/route.ts b/src/app/api/auth/clear-flow/route.ts new file mode 100644 index 00000000..c5fc8683 --- /dev/null +++ b/src/app/api/auth/clear-flow/route.ts @@ -0,0 +1,19 @@ +// app/api/auth/clear-flow/route.ts +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; + +export async function POST() { + try { + // ✅ Next.js 15 syntax + const cookieStore = await cookies(); + cookieStore.delete('auth_flow'); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('❌ Error clearing flow cookie:', error); + return NextResponse.json( + { success: false, message: 'Internal server error' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/auth/get-flow/route.ts b/src/app/api/auth/get-flow/route.ts new file mode 100644 index 00000000..29ab4e24 --- /dev/null +++ b/src/app/api/auth/get-flow/route.ts @@ -0,0 +1,22 @@ +// app/api/auth/get-flow/route.ts +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; + +export async function GET() { + try { + // ✅ Next.js 15 syntax + const cookieStore = await cookies(); + const flow = cookieStore.get('auth_flow')?.value || 'login'; + + return NextResponse.json({ + success: true, + flow + }); + } catch (error) { + console.error('❌ Error getting flow cookie:', error); + return NextResponse.json( + { success: false, flow: 'login' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 96333148..eea1caa6 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -2,6 +2,7 @@ import prisma from "@/lib/prisma"; import { NextResponse } from "next/server"; import { randomOTP } from "../_lib/randomOTP"; +import { cookies } from "next/headers"; export async function POST(req: Request) { if (req.method !== "POST") { @@ -29,19 +30,37 @@ export async function POST(req: Request) { const isRegistered = !!existingUser; if (isRegistered) { - // ✅ User terdaftar → kirim OTP const codeOtp = randomOTP(); const otpNumber = Number(codeOtp); - const waMessage = `Website Desa Darmasaba - Kode verifikasi Anda: ${codeOtp}`; - const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`; + // ✅ PERBAIKAN: Gunakan format pesan yang lebih sederhana + // Hapus karakter khusus yang bisa bikin masalah + const waMessage = `Website Desa Darmasaba\nKode verifikasi Anda ${codeOtp}`; + + // ✅ OPSI 1: Tanpa encoding (coba dulu ini) + const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${waMessage}`; + + // ✅ OPSI 2: Dengan encoding (kalau opsi 1 gagal) + // const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${encodeURIComponent(waMessage)}`; + + // ✅ OPSI 3: Encoding manual untuk URL-safe (alternatif terakhir) + // const encodedMessage = waMessage.replace(/\n/g, '%0A').replace(/ /g, '%20'); + // const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${encodedMessage}`; + console.log("🔍 Debug WA URL:", waUrl); // Untuk debugging + const res = await fetch(waUrl); const sendWa = await res.json(); + console.log("📱 WA Response:", sendWa); // Debug response + if (sendWa.status !== "success") { return NextResponse.json( - { success: false, message: "Gagal mengirim OTP via WhatsApp" }, + { + success: false, + message: "Gagal mengirim OTP via WhatsApp", + debug: sendWa // Tampilkan error detail + }, { status: 400 } ); } @@ -50,6 +69,15 @@ export async function POST(req: Request) { data: { nomor, otp: otpNumber, isActive: true }, }); + const cookieStore = await cookies(); + cookieStore.set('auth_flow', 'login', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 5, // 5 menit + path: '/' + }); + return NextResponse.json({ success: true, message: "Kode verifikasi dikirim", @@ -57,16 +85,14 @@ export async function POST(req: Request) { isRegistered: true, }); } else { - // ❌ User belum terdaftar → JANGAN kirim OTP return NextResponse.json({ success: true, message: "Nomor belum terdaftar", isRegistered: false, - // Tidak ada kodeId }); } } catch (error) { - console.error("Error Login:", error); + console.error("❌ Error Login:", error); return NextResponse.json( { success: false, message: "Terjadi kesalahan saat login" }, { status: 500 } diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index ff87c8e3..0ffac336 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; import prisma from '@/lib/prisma'; -import { randomOTP } from '../_lib/randomOTP'; // pastikan ada +import { randomOTP } from '../_lib/randomOTP'; export async function POST(req: Request) { try { @@ -36,7 +37,17 @@ export async function POST(req: Request) { data: { nomor, otp: otpNumber, isActive: true } }); - // ✅ Kembalikan kodeId (jangan buat user di sini!) + // ✅ Set cookie flow=register (Next.js 15+ syntax) + const cookieStore = await cookies(); + cookieStore.set('auth_flow', 'register', { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 5, // 5 menit + path: '/' + }); + + // ✅ Kembalikan kodeId return NextResponse.json({ success: true, message: 'Kode verifikasi dikirim', diff --git a/src/app/api/auth/set-flow/route.ts b/src/app/api/auth/set-flow/route.ts new file mode 100644 index 00000000..b53d9eab --- /dev/null +++ b/src/app/api/auth/set-flow/route.ts @@ -0,0 +1,34 @@ +// app/api/auth/set-flow/route.ts +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; + +export async function POST(request: Request) { + try { + const { flow } = await request.json(); + + if (!flow || !['login', 'register'].includes(flow)) { + return NextResponse.json( + { success: false, message: 'Invalid flow parameter' }, + { status: 400 } + ); + } + + // ✅ Next.js 15 syntax + const cookieStore = await cookies(); + cookieStore.set('auth_flow', flow, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 5, + path: '/' + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('❌ Error setting flow cookie:', error); + return NextResponse.json( + { success: false, message: 'Internal server error' }, + { status: 500 } + ); + } +} \ No newline at end of file