feat: remove backend link and support passwordless login and registration
This commit is contained in:
parent
b3557d0698
commit
2a8f575e1c
6 changed files with 59 additions and 37 deletions
|
|
@ -267,5 +267,34 @@ describe('Supplier Portal Onboarding & Active Dashboard Tests', () => {
|
||||||
|
|
||||||
globalThis.fetch = originalFetch
|
globalThis.fetch = originalFetch
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('allows login directly without entering any password', async () => {
|
||||||
|
render(<App />)
|
||||||
|
|
||||||
|
const loginBtn = screen.getByText(/^Login$/i)
|
||||||
|
fireEvent.click(loginBtn)
|
||||||
|
|
||||||
|
const submitBtn = screen.getByRole('button', { name: /Sign In/i })
|
||||||
|
fireEvent.click(submitBtn)
|
||||||
|
|
||||||
|
expect(await screen.findByText(/Total Sales/i)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows signup without entering any password', async () => {
|
||||||
|
render(<App />)
|
||||||
|
|
||||||
|
const getStartedBtn = screen.getByText(/^Get started$/i)
|
||||||
|
fireEvent.click(getStartedBtn)
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByPlaceholderText(/Enter your email/i), { target: { value: 'artisan_nopass@tradhox.com' } })
|
||||||
|
fireEvent.change(screen.getByPlaceholderText(/9876543210/i), { target: { value: '9876543210' } })
|
||||||
|
fireEvent.click(screen.getByLabelText(/I agree to the/i))
|
||||||
|
|
||||||
|
const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => {})
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /Create Account/i }))
|
||||||
|
|
||||||
|
expect(await screen.findByText(/Welcome to Tradhox/i)).toBeInTheDocument()
|
||||||
|
alertMock.mockRestore()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,10 +150,9 @@ export default function AuthForms() {
|
||||||
<input
|
<input
|
||||||
id="r-pass"
|
id="r-pass"
|
||||||
type={showSignupPass ? 'text' : 'password'}
|
type={showSignupPass ? 'text' : 'password'}
|
||||||
placeholder="Create a password"
|
placeholder="Create a password (optional)"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={e => setPassword(e.target.value)}
|
onChange={e => setPassword(e.target.value)}
|
||||||
required
|
|
||||||
style={{ width: '100%', border: '1px solid #D1CAC7', borderRadius: '8px', height: '42px', padding: '0 40px 0 14px', fontSize: '0.9rem', outline: 'none' }}
|
style={{ width: '100%', border: '1px solid #D1CAC7', borderRadius: '8px', height: '42px', padding: '0 40px 0 14px', fontSize: '0.9rem', outline: 'none' }}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|
@ -174,10 +173,9 @@ export default function AuthForms() {
|
||||||
<input
|
<input
|
||||||
id="r-confirm"
|
id="r-confirm"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Repeat your password"
|
placeholder="Repeat your password (optional)"
|
||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={e => setConfirmPassword(e.target.value)}
|
onChange={e => setConfirmPassword(e.target.value)}
|
||||||
required
|
|
||||||
style={{ width: '100%', border: '1px solid #D1CAC7', borderRadius: '8px', height: '42px', padding: '0 14px', fontSize: '0.9rem', outline: 'none' }}
|
style={{ width: '100%', border: '1px solid #D1CAC7', borderRadius: '8px', height: '42px', padding: '0 14px', fontSize: '0.9rem', outline: 'none' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,6 @@ export default function LoginPage() {
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
required
|
|
||||||
style={{ borderColor: '#D1CAC7', borderRadius: '8px', height: '42px', fontSize: '0.9rem' }}
|
style={{ borderColor: '#D1CAC7', borderRadius: '8px', height: '42px', fontSize: '0.9rem' }}
|
||||||
/>
|
/>
|
||||||
<span className="icon is-left has-text-grey">
|
<span className="icon is-left has-text-grey">
|
||||||
|
|
@ -237,7 +236,6 @@ export default function LoginPage() {
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
|
||||||
style={{ borderColor: '#D1CAC7', borderRadius: '8px', height: '42px', fontSize: '0.9rem' }}
|
style={{ borderColor: '#D1CAC7', borderRadius: '8px', height: '42px', fontSize: '0.9rem' }}
|
||||||
/>
|
/>
|
||||||
<span className="icon is-left has-text-grey">
|
<span className="icon is-left has-text-grey">
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
const getApiBaseUrl = () => {
|
const getApiBaseUrl = () => {
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
return 'http://127.0.0.1:8000'; // Or your local backend port
|
|
||||||
}
|
|
||||||
// Use relative path in production.
|
|
||||||
// CloudFront will proxy /api/* requests to the EC2 backend automatically!
|
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -479,26 +479,22 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
||||||
const handleRegisterSubmit = (e: React.FormEvent) => {
|
const handleRegisterSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
// Email Validation
|
// Email Validation (if provided)
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!emailRegex.test(email)) {
|
if (email.trim() && !emailRegex.test(email)) {
|
||||||
alert('Please enter a valid email address.');
|
alert('Please enter a valid email address.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phone Validation
|
// Phone Validation (if provided)
|
||||||
const phoneRegex = /^[6-9]\d{9}$/;
|
const phoneRegex = /^[6-9]\d{9}$/;
|
||||||
if (!phoneRegex.test(phone)) {
|
if (phone.trim() && !phoneRegex.test(phone)) {
|
||||||
alert('Please enter a valid 10-digit mobile number starting with 6, 7, 8, or 9.');
|
alert('Please enter a valid 10-digit mobile number starting with 6, 7, 8, or 9.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password Validation
|
// Password Validation: If password is provided and confirm password doesn't match
|
||||||
if (password.length < 8) {
|
if (password && confirmPassword && password !== confirmPassword) {
|
||||||
alert('Password must be at least 8 characters long.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
alert('Passwords do not match.');
|
alert('Passwords do not match.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -509,15 +505,26 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const completeRegistration = () => {
|
||||||
|
localStorage.setItem('access_token', 'demo_access_token');
|
||||||
|
navigateTo('profile-completion');
|
||||||
|
};
|
||||||
|
|
||||||
|
// If no password is provided, proceed directly to onboarding
|
||||||
|
if (!password) {
|
||||||
|
completeRegistration();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/register/`, {
|
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/register/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: email,
|
username: email || 'demo_artisan@tradhox.com',
|
||||||
email: email,
|
email: email || 'demo_artisan@tradhox.com',
|
||||||
phone: phone,
|
phone: phone || '9876543210',
|
||||||
password: password,
|
password: password,
|
||||||
confirm_password: confirmPassword,
|
confirm_password: confirmPassword || password,
|
||||||
policy_accepted: policyAccepted
|
policy_accepted: policyAccepted
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -530,7 +537,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
||||||
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, {
|
apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ username: email, password: password })
|
body: JSON.stringify({ username: email || 'demo_artisan@tradhox.com', password: password })
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (!res.ok) throw new Error('Auto-login failed.');
|
if (!res.ok) throw new Error('Auto-login failed.');
|
||||||
|
|
@ -541,11 +548,13 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
||||||
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
|
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
|
||||||
navigateTo('profile-completion')
|
navigateTo('profile-completion')
|
||||||
})
|
})
|
||||||
|
.catch(() => {
|
||||||
|
completeRegistration();
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// Offline / demo fallback
|
// Offline / demo fallback
|
||||||
localStorage.setItem('access_token', 'demo_access_token');
|
completeRegistration();
|
||||||
navigateTo('profile-completion');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,7 +571,8 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
||||||
navigateTo('dashboard', true);
|
navigateTo('dashboard', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!email.trim()) {
|
// Allow login without password directly
|
||||||
|
if (!password) {
|
||||||
fallbackDemoLogin();
|
fallbackDemoLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,4 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
],
|
],
|
||||||
server: {
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://127.0.0.1:8000',
|
|
||||||
changeOrigin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue