Login page Authenticate and input icon
This commit is contained in:
21
.env.unified
Normal file
21
.env.unified
Normal file
@@ -0,0 +1,21 @@
|
||||
# Build profile for a bundle that is served by the FastAPI backend itself.
|
||||
#
|
||||
# Use it with: npm run build:unified
|
||||
#
|
||||
# Why this file exists: `npm run build` loads .env.production, which pins
|
||||
# VITE_API_BASE_URL to https://mcp.nearle.ai.in. That is correct for the
|
||||
# deployed site (served from catalogue.nearle.ai.in, calling the API on its
|
||||
# own subdomain) but wrong for every local build, because backend/app/main.py
|
||||
# serves frontend/dist automatically whenever it exists. Opening
|
||||
# http://localhost:8000 then loads a bundle that sends /api/auth/login to the
|
||||
# remote host instead of the backend sitting on the same port - so logging in
|
||||
# with the local admin credentials fails with "Could not reach the API",
|
||||
# which reads like the backend being down when it is running fine.
|
||||
#
|
||||
# Blank means requests stay relative ("/api/..."), i.e. same-origin, so they
|
||||
# land on whichever server delivered the page. That is exactly what the
|
||||
# single-port unified deployment needs, and it needs no CORS entry at all.
|
||||
#
|
||||
# Same value and same reasoning as .env.development; separate file because
|
||||
# Vite only loads .env.development in dev mode, never during a build.
|
||||
VITE_API_BASE_URL=
|
||||
18
README.md
18
README.md
@@ -42,10 +42,26 @@ If your backend runs somewhere else, set `VITE_API_BASE_URL` in a local
|
||||
## Production build
|
||||
|
||||
```bash
|
||||
npm run build # outputs to dist/
|
||||
npm run build # outputs to dist/ - for the DEPLOYED site only
|
||||
npm run preview # serve the production build locally to sanity-check it
|
||||
```
|
||||
|
||||
`npm run build` loads `.env.production`, which pins `VITE_API_BASE_URL` to
|
||||
`https://mcp.nearle.ai.in`. That is right for the deployed site and wrong for
|
||||
anything you run locally, because the backend serves `frontend/dist` itself
|
||||
whenever that directory exists - so `http://localhost:8000` would hand you a
|
||||
page that sends `/api/auth/login` to the remote host and fails with "Could not
|
||||
reach the API" even though the local backend is up. For a build you intend to
|
||||
run locally, use:
|
||||
|
||||
```bash
|
||||
npm run build:unified # same optimised build, blank VITE_API_BASE_URL
|
||||
```
|
||||
|
||||
That leaves API calls relative, so they go back to whichever server delivered
|
||||
the page - port 8000 for the backend's single-port mode, or `npm run preview`,
|
||||
which proxies `/api/*` to `127.0.0.1:8000` the same way the dev server does.
|
||||
|
||||
## Pages
|
||||
|
||||
- `/` - Browse & Search (sidebar brand/category filters, product grid,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build:unified": "vite build --mode unified",
|
||||
"lint": "oxlint",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../context/useAuth';
|
||||
import { ShieldCheck, Lock, User, ArrowRight, Sparkles } from 'lucide-react';
|
||||
import { ShieldCheck, Lock, User, ArrowRight, Sparkles, Eye, EyeOff } from 'lucide-react';
|
||||
|
||||
/*
|
||||
* There is exactly one interactive account: `admin`.
|
||||
@@ -19,6 +19,7 @@ export function LoginPage() {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -96,14 +97,36 @@ export function LoginPage() {
|
||||
<label className="block text-xs font-medium text-slate-600 mb-1.5">Password</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
|
||||
{/* pr-11, unlike the username field's pr-4, keeps a long
|
||||
password from running underneath the toggle button. */}
|
||||
<input
|
||||
type="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="Enter password"
|
||||
className="w-full rounded-xl border border-ink-900/15 bg-white py-2.5 pl-9 pr-4 text-sm text-ink-950 placeholder-slate-400 focus:border-amber-500 focus:outline-none focus:ring-2 focus:ring-amber-500/25"
|
||||
className="w-full rounded-xl border border-ink-900/15 bg-white py-2.5 pl-9 pr-11 text-sm text-ink-950 placeholder-slate-400 focus:border-amber-500 focus:outline-none focus:ring-2 focus:ring-amber-500/25"
|
||||
/>
|
||||
{/*
|
||||
type="button" is load-bearing: a <button> inside a <form>
|
||||
defaults to type="submit", so peeking at the password would
|
||||
fire a half-typed login attempt instead of revealing it.
|
||||
|
||||
The icon shows the ACTION, not the state - an open eye means
|
||||
"reveal", which is the convention every browser and password
|
||||
manager uses. aria-pressed carries the state for screen
|
||||
readers, which the glyph alone cannot.
|
||||
*/}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword((shown) => !shown)}
|
||||
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||
aria-pressed={showPassword}
|
||||
title={showPassword ? 'Hide password' : 'Show password'}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-lg p-1.5 text-slate-400 hover:text-slate-600 hover:bg-ink-900/5 focus:outline-none focus:ring-2 focus:ring-amber-500/25 transition-colors cursor-pointer"
|
||||
>
|
||||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,19 +2,29 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
// Both dev and preview forward /api/* to FastAPI, so a same-origin (relative)
|
||||
// request works whichever local server is delivering the page. Keep the two in
|
||||
// sync: `preview` serves dist/, and without this it would answer /api with a
|
||||
// 404 index.html, which surfaces in the UI as a login failure.
|
||||
const apiProxy = {
|
||||
'/api': {
|
||||
// Proxy all /api/* requests straight to FastAPI on port 8000
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
},
|
||||
}
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
// Proxy all /api/* requests straight to FastAPI on port 8000
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
proxy: apiProxy,
|
||||
},
|
||||
preview: {
|
||||
port: 4173,
|
||||
proxy: apiProxy,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user