feat(auth): display confirmation banner and require email verification
Adds a confirmation banner on the login page after a user successfully registers a new account. This provides clear user feedback that their registration was successful and that an email has been sent for account verification. Also prevents users from logging in without a confirmed email address by updating the registration and new account creation flows to set the 'confirmed' status to false by default. A confirmation token is generated and a confirmation link is logged to the console (for now) to enable email verification.
This commit is contained in:
@@ -25,7 +25,7 @@ const Order = () => {
|
||||
const navigate = useNavigate();
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pricing, setPricing] = useState({ price_per_gallon: 0, date: null });
|
||||
const [pricing, setPricing] = useState({ price_per_gallon: 0, price_same_day: 0, price_prime: 0, price_emergency: 0, date: null });
|
||||
const [totalPrice, setTotalPrice] = useState(0);
|
||||
const [selectedGallons, setSelectedGallons] = useState(100);
|
||||
const [selectedDate, setSelectedDate] = useState(dayjs().add(1, 'day'));
|
||||
@@ -36,7 +36,7 @@ const Order = () => {
|
||||
gallons_ordered: 100,
|
||||
expected_delivery_date: dayjs().add(1, 'day'),
|
||||
dispatcher_notes: '',
|
||||
payment_type: 1, // credit
|
||||
payment_type: 11, // credit (Authorize.net)
|
||||
});
|
||||
const [notesLength, setNotesLength] = useState(0);
|
||||
|
||||
@@ -126,12 +126,12 @@ const Order = () => {
|
||||
};
|
||||
|
||||
const handleGallonsChange = (value) => {
|
||||
setFormData({ ...formData, gallons_ordered: value });
|
||||
setFormData(prev => ({ ...prev, gallons_ordered: value }));
|
||||
setSelectedGallons(value);
|
||||
};
|
||||
|
||||
const handleDateChange = (date) => {
|
||||
setFormData({ ...formData, expected_delivery_date: date });
|
||||
setFormData(prev => ({ ...prev, expected_delivery_date: date }));
|
||||
setSelectedDate(date);
|
||||
};
|
||||
|
||||
@@ -157,9 +157,9 @@ const Order = () => {
|
||||
// Get surcharge based on service type and prime
|
||||
const getSurcharge = () => {
|
||||
let surcharge = 0;
|
||||
if (serviceType === 'same_day') surcharge += 45;
|
||||
if (serviceType === 'emergency') surcharge += 250;
|
||||
if (primeSelected) surcharge += serviceType === 'emergency' ? 50 : 25;
|
||||
if (serviceType === 'same_day') surcharge += pricing.price_same_day;
|
||||
if (serviceType === 'emergency') surcharge += pricing.price_emergency;
|
||||
if (primeSelected) surcharge += pricing.price_prime;
|
||||
return surcharge;
|
||||
};
|
||||
|
||||
@@ -247,7 +247,7 @@ const Order = () => {
|
||||
const onFinish = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (formData.payment_type === 1) {
|
||||
if (formData.payment_type === 11) {
|
||||
// Validate card selection
|
||||
if (!selectedCardId) {
|
||||
message.error('Please select a payment card or add a new one');
|
||||
@@ -288,7 +288,7 @@ const Order = () => {
|
||||
gallons_ordered: 100,
|
||||
expected_delivery_date: dayjs().add(1, 'day'),
|
||||
dispatcher_notes: '',
|
||||
payment_type: 1,
|
||||
payment_type: 11,
|
||||
});
|
||||
setServiceType(null);
|
||||
setPrimeSelected(false);
|
||||
@@ -318,7 +318,7 @@ const Order = () => {
|
||||
gallons_ordered: 100,
|
||||
expected_delivery_date: dayjs().add(1, 'day'),
|
||||
dispatcher_notes: '',
|
||||
payment_type: 1,
|
||||
payment_type: 11,
|
||||
});
|
||||
setServiceType(null);
|
||||
setPrimeSelected(false);
|
||||
@@ -411,7 +411,7 @@ const Order = () => {
|
||||
onClick={handleSameDaySelect}
|
||||
style={{ marginRight: 10 }}
|
||||
>
|
||||
Same Day (+$45)
|
||||
Same Day (+${pricing.price_same_day})
|
||||
</Button>
|
||||
)}
|
||||
{!isSameDayAvailable() && (
|
||||
@@ -423,7 +423,7 @@ const Order = () => {
|
||||
type={serviceType === 'emergency' ? "primary" : "default"}
|
||||
onClick={handleEmergencySelect}
|
||||
>
|
||||
Emergency (+$250)
|
||||
Emergency (+${pricing.price_emergency})
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -446,7 +446,7 @@ const Order = () => {
|
||||
type={primeSelected ? "primary" : "default"}
|
||||
onClick={() => setPrimeSelected(!primeSelected)}
|
||||
>
|
||||
Prime (+${serviceType === 'emergency' ? 50 : 25})
|
||||
Prime (+${pricing.price_prime})
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -475,10 +475,10 @@ const Order = () => {
|
||||
<Form.Item label="Payment Method">
|
||||
<Radio.Group onChange={handlePaymentChange} value={formData.payment_type}>
|
||||
<Radio value={0}>Cash</Radio>
|
||||
<Radio value={1}>Credit</Radio>
|
||||
<Radio value={11}>Credit</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
{formData.payment_type === 1 && (
|
||||
{formData.payment_type === 11 && (
|
||||
<div>
|
||||
{loadingCards ? (
|
||||
<Card style={{ textAlign: 'center', padding: spacing.lg }}>
|
||||
@@ -561,17 +561,17 @@ const Order = () => {
|
||||
<Descriptions.Item label="Gallons">{formData.gallons_ordered}</Descriptions.Item>
|
||||
<Descriptions.Item label="Price per Gallon">${pricing.price_per_gallon.toFixed(2)}</Descriptions.Item>
|
||||
{serviceType && (
|
||||
<Descriptions.Item label="Service Surcharge">${serviceType === 'same_day' ? 45 : 250} ({serviceType === 'same_day' ? 'Same Day' : 'Emergency'})</Descriptions.Item>
|
||||
<Descriptions.Item label="Service Surcharge">${serviceType === 'same_day' ? pricing.price_same_day : pricing.price_emergency} ({serviceType === 'same_day' ? 'Same Day' : 'Emergency'})</Descriptions.Item>
|
||||
)}
|
||||
{primeSelected && (
|
||||
<Descriptions.Item label="Prime Surcharge">${serviceType === 'emergency' ? 50 : 25}</Descriptions.Item>
|
||||
<Descriptions.Item label="Prime Surcharge">${pricing.price_prime}</Descriptions.Item>
|
||||
)}
|
||||
<Descriptions.Item label="Total Amount">${totalPrice.toFixed(2)}</Descriptions.Item>
|
||||
<Descriptions.Item label="Delivery Date">{formData.expected_delivery_date.format('YYYY-MM-DD')}</Descriptions.Item>
|
||||
<Descriptions.Item label="Notes">{formData.dispatcher_notes || 'None'}</Descriptions.Item>
|
||||
<Descriptions.Item label="Payment Method">{formData.payment_type === 0 ? 'Cash' : 'Credit Card'}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{formData.payment_type === 1 && getSelectedCard() && (
|
||||
{formData.payment_type === 11 && getSelectedCard() && (
|
||||
<Card
|
||||
style={{
|
||||
marginTop: 16,
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Form, Input, Button, Card, message } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Form, Input, Button, Card, message, Alert } from 'antd';
|
||||
import { MailOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useNavigate, Link, useLocation } from 'react-router-dom';
|
||||
import api from '../../utils/api';
|
||||
import { commonStyles } from '../../theme';
|
||||
|
||||
function Login() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [showConfirmationBanner, setShowConfirmationBanner] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.get('registered') === 'true') {
|
||||
setShowConfirmationBanner(true);
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
const onFinish = async (values) => {
|
||||
try {
|
||||
@@ -21,6 +30,17 @@ function Login() {
|
||||
|
||||
return (
|
||||
<Card title="Login" style={{ maxWidth: 400, margin: '0 auto', ...commonStyles.cardGray }}>
|
||||
{showConfirmationBanner && (
|
||||
<Alert
|
||||
message="Registration Successful"
|
||||
description="An email has been sent to your email address with instructions to confirm your account."
|
||||
type="success"
|
||||
showIcon
|
||||
closable
|
||||
onClose={() => setShowConfirmationBanner(false)}
|
||||
style={{ marginBottom: '16px' }}
|
||||
/>
|
||||
)}
|
||||
<Form
|
||||
name="login"
|
||||
onFinish={onFinish}
|
||||
|
||||
@@ -11,8 +11,8 @@ function Register() {
|
||||
const onFinish = async (values) => {
|
||||
try {
|
||||
await api.post('/auth/register', values);
|
||||
message.success('Registration successful, please login');
|
||||
navigate('/login');
|
||||
message.success('Registration successful, please check your email to confirm your account');
|
||||
navigate('/login?registered=true');
|
||||
} catch (error) {
|
||||
message.error('Registration failed: ' + (error.response?.data?.detail || 'Unknown error'));
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ function New() {
|
||||
}
|
||||
|
||||
message.success('Customer registration completed successfully!');
|
||||
navigate('/');
|
||||
navigate('/login?registered=true');
|
||||
} catch (error) {
|
||||
if (error.response?.status === 400 && error.response.data?.detail) {
|
||||
message.error(error.response.data.detail);
|
||||
|
||||
Reference in New Issue
Block a user