forked from hazem/yslootahrobotics
- Implemented Prisma schema with models for AdminUser, AppSettings, and Snapshot. - Created seed script to initialize the database with an admin user and JWT secret. - Developed admin login page with form handling and error management. - Added API routes for admin login, logout, change password, and JWT verification. - Integrated Stripe for payment intent management in admin orders. - Established middleware for protecting admin routes with JWT authentication. - Created Zustand stores for managing persona and snapshot states.
86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
|
import { orderStore } from '@/store/useOrderStore';
|
|
|
|
export function PaymentStep() {
|
|
const stripe = useStripe();
|
|
const elements = useElements();
|
|
const [errorMsg, setErrorMsg] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!stripe || !elements) return;
|
|
|
|
setIsLoading(true);
|
|
setErrorMsg('');
|
|
|
|
// Validate the payment element first
|
|
const { error: submitError } = await elements.submit();
|
|
if (submitError) {
|
|
setErrorMsg(submitError.message || 'Validation failed');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
// Move to review — actual confirmation happens on "Place Order"
|
|
orderStore.getState().setPayment({ status: 'idle' });
|
|
orderStore.getState().setStep('review');
|
|
setIsLoading(false);
|
|
};
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
<h3 style={{ fontSize: '1rem', fontWeight: 600, color: '#1a1a2e', margin: 0 }}>
|
|
Payment Details
|
|
</h3>
|
|
|
|
<div style={{
|
|
padding: '1rem',
|
|
borderRadius: '0.5rem',
|
|
background: '#fff',
|
|
border: '1px solid rgba(0, 0, 0, 0.08)',
|
|
}}>
|
|
<PaymentElement
|
|
options={{
|
|
layout: 'tabs',
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{errorMsg && (
|
|
<div style={{
|
|
padding: '0.6rem 0.75rem',
|
|
borderRadius: '0.375rem',
|
|
background: 'rgba(239, 68, 68, 0.06)',
|
|
border: '1px solid rgba(239, 68, 68, 0.15)',
|
|
fontSize: '0.75rem',
|
|
color: '#dc2626',
|
|
}}>
|
|
{errorMsg}
|
|
</div>
|
|
)}
|
|
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={!stripe || isLoading}
|
|
style={{
|
|
marginTop: '0.5rem',
|
|
padding: '0.75rem',
|
|
borderRadius: '0.375rem',
|
|
border: '1px solid rgba(59, 130, 246, 0.5)',
|
|
background: isLoading ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.08)',
|
|
color: '#2563eb',
|
|
cursor: isLoading ? 'wait' : 'pointer',
|
|
fontSize: '0.85rem',
|
|
fontWeight: 600,
|
|
transition: 'all 0.2s ease',
|
|
}}
|
|
>
|
|
{isLoading ? 'Validating...' : 'Review Order'}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|