Source identity
| Field | Recorded value |
|---|---|
| Source type | archive member |
| Original record | AuthModal.tsx |
| Date | Not specified in source |
| Path or archive member | archives/libsrc-08bde1de6666/NETLIFY_AI_RECOVERY_AUDIT_2026-10-14_v1/additional_recovery/route_builder_sources_from_maps/embedded_sources/AuthModal.tsx |
| Original SHA-256 | 1deb362a634349d90197fcf9974e24b9cf25f3a6d248d16515e479974612d6e9 |
| Reading-copy SHA-256 | 970b01a3f77a6e2106548c16e861e198fb5ac9afe071efaa6c59922dfaf345e7 |
| Archive source | {"sourceId": "libsrc-08bde1de6666", "member": "NETLIFY_AI_RECOVERY_AUDIT_2026-10-14_v1/additional_recovery/route_builder_sources_from_maps/embedded_sources/AuthModal.tsx"} |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| AuthModal.tsx | {"sourceId": "libsrc-08bde1de6666", "member": "NETLIFY_AI_RECOVERY_AUDIT_2026-10-14_v1/additional_recovery/route_builder_sources_from_maps/embedded_sources/AuthModal.tsx"} | libsrc-7be568d89b39 | archives/libsrc-08bde1de6666/NETLIFY_AI_RECOVERY_AUDIT_2026-10-14_v1/additional_recovery/route_builder_sources_from_maps/embedded_sources/AuthModal.tsx |
| Janady13/oversize-route-builder / client/src/AuthModal.tsx | main | 8630196cd162b68cf069cdabcc5487b49dcc625d | Open original |
Recovered source transcript
Download complete source text ↓223 lines · 7.6 KB
import React, { useState } from 'react';
// Using simple Unicode/icons to avoid React type conflicts in CI build
// import { FaTimes, FaSpinner } from 'react-icons/fa';
import axios from 'axios';
interface AuthModalProps {
mode: 'login' | 'register';
onClose: () => void;
onSwitchMode: (mode: 'login' | 'register') => void;
}
const AuthModal: React.FC<AuthModalProps> = ({ mode, onClose, onSwitchMode }) => {
const [formData, setFormData] = useState({
email: '',
password: '',
companyName: '',
usdotNumber: '',
phone: ''
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
setError('');
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
setSuccess('');
try {
const endpoint = mode === 'login' ? '/api/auth/login' : '/api/auth/register';
const base =
process.env.REACT_APP_API_URL ||
(process.env.NODE_ENV === 'production' ? '' : 'http://localhost:5000');
const response = await axios.post(`${base}${endpoint}`, formData);
if (mode === 'login') {
// Store token and redirect to dashboard
localStorage.setItem('token', response.data.token);
localStorage.setItem('user', JSON.stringify(response.data.user));
window.location.href = '/dashboard';
} else {
// Show success message
setSuccess('Registration successful! Please check your email to verify your account.');
setTimeout(() => {
onSwitchMode('login');
}, 3000);
}
} catch (err: any) {
setError(err.response?.data?.error?.message || 'An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="flex justify-between items-center p-6 border-b">
<h2 className="text-2xl font-bold text-gray-900">
{mode === 'login' ? 'Sign In' : 'Create Account'}
</h2>
<button
onClick={onClose}
aria-label="Close"
className="text-gray-400 hover:text-gray-600 transition text-xl leading-none"
>
×
</button>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="p-6 space-y-4">
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
{error}
</div>
)}
{success && (
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm">
{success}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
placeholder="[author/contact email redacted]"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
required
minLength={8}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
placeholder="••••••••"
/>
{mode === 'register' && (
<p className="mt-1 text-xs text-gray-500">
Must be at least 8 characters
</p>
)}
</div>
{mode === 'register' && (
<>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Company Name *
</label>
<input
type="text"
name="companyName"
value={formData.companyName}
onChange={handleChange}
required
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
placeholder="Your Company LLC"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
USDOT Number (Optional)
</label>
<input
type="text"
name="usdotNumber"
value={formData.usdotNumber}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
placeholder="1234567"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Phone Number (Optional)
</label>
<input
type="tel"
name="phone"
value={formData.phone}
onChange={handleChange}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
placeholder="(555) 123-4567"
/>
</div>
</>
)}
<button
type="submit"
disabled={loading}
className="w-full bg-primary-600 text-white py-3 rounded-lg font-semibold hover:bg-primary-700 transition disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
>
{loading ? (
<>
<span className="inline-block animate-spin mr-2" aria-hidden>⏳</span>
{mode === 'login' ? 'Signing in...' : 'Creating account...'}
</>
) : (
mode === 'login' ? 'Sign In' : 'Create Account'
)}
</button>
{mode === 'login' && (
<div className="text-center">
<button
type="button"
className="text-sm text-primary-600 hover:text-primary-700"
>
Forgot password?
</button>
</div>
)}
</form>
{/* Footer */}
<div className="px-6 pb-6 text-center border-t pt-6">
<p className="text-sm text-gray-600">
{mode === 'login' ? "Don't have an account? " : "Already have an account? "}
<button
onClick={() => onSwitchMode(mode === 'login' ? 'register' : 'login')}
className="text-primary-600 hover:text-primary-700 font-semibold"
>
{mode === 'login' ? 'Sign up' : 'Sign in'}
</button>
</p>
</div>
</div>
</div>
);
};
export default AuthModal;