Changed login screen to ask for username and password instead of displaying staff name
This commit is contained in:
@@ -14,17 +14,28 @@ function useApiData(authed) {
|
|||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!authed) return;
|
|
||||||
|
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!authed) {
|
||||||
|
// Fetch only public data (workspace info and user list for login)
|
||||||
|
const [users, workspace] = await Promise.all([
|
||||||
|
api.getUsers().catch(() => []),
|
||||||
|
api.getWorkspace().catch(() => null)
|
||||||
|
]);
|
||||||
|
if (mounted) {
|
||||||
|
setData(prev => ({ ...prev, users, workspace }));
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const [tasks, users, audit, workspace, deletedTasks] = await Promise.all([
|
const [tasks, users, audit, workspace, deletedTasks] = await Promise.all([
|
||||||
api.getTasks(),
|
api.getTasks(),
|
||||||
api.getUsers(),
|
api.getUsers(),
|
||||||
api.getAudit(),
|
api.getAudit(),
|
||||||
api.getWorkspace(),
|
api.getWorkspace(),
|
||||||
api.getDeletedTasks().catch(() => []) // Catch if not admin or error
|
api.getDeletedTasks().catch(() => [])
|
||||||
]);
|
]);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setData({ tasks, users, audit, workspace, deletedTasks });
|
setData({ tasks, users, audit, workspace, deletedTasks });
|
||||||
@@ -109,8 +120,14 @@ function App() {
|
|||||||
|
|
||||||
if (!authed) {
|
if (!authed) {
|
||||||
return <LoginScreen dbUsers={dbUsers} workspace={workspace} onLogin={async (id, pwd) => {
|
return <LoginScreen dbUsers={dbUsers} workspace={workspace} onLogin={async (id, pwd) => {
|
||||||
await api.login(id, pwd);
|
const data = await api.login(id, pwd);
|
||||||
setMeId(id);
|
// Extract actual User ID from token payload
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(atob(data.access_token.split('.')[1]));
|
||||||
|
setMeId(payload.sub);
|
||||||
|
} catch(e) {
|
||||||
|
setMeId(id);
|
||||||
|
}
|
||||||
setAuthed(true);
|
setAuthed(true);
|
||||||
api.addAudit({ actor: id, action: 'login', summary: 'Signed in' }).catch(console.error);
|
api.addAudit({ actor: id, action: 'login', summary: 'Signed in' }).catch(console.error);
|
||||||
}} />;
|
}} />;
|
||||||
|
|||||||
+8
-3
@@ -21,7 +21,12 @@ app.add_middleware(
|
|||||||
|
|
||||||
@app.post("/token", response_model=schemas.Token)
|
@app.post("/token", response_model=schemas.Token)
|
||||||
async def login_for_access_token(form_data: schemas.UserLogin, db: Session = Depends(get_db)):
|
async def login_for_access_token(form_data: schemas.UserLogin, db: Session = Depends(get_db)):
|
||||||
user = db.query(models.User).filter(models.User.id == form_data.id).first()
|
# Search by ID or Name
|
||||||
|
user = db.query(models.User).filter(
|
||||||
|
(models.User.id == form_data.id) |
|
||||||
|
(models.User.name == form_data.id)
|
||||||
|
).first()
|
||||||
|
|
||||||
if not user or not auth.verify_password(form_data.password, user.password_hash):
|
if not user or not auth.verify_password(form_data.password, user.password_hash):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
@@ -32,7 +37,7 @@ async def login_for_access_token(form_data: schemas.UserLogin, db: Session = Dep
|
|||||||
return {"access_token": access_token, "token_type": "bearer"}
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
||||||
@app.get("/users", response_model=List[schemas.User])
|
@app.get("/users", response_model=List[schemas.User])
|
||||||
def read_users(db: Session = Depends(get_db), current_user: models.User = Depends(auth.get_current_user)):
|
def read_users(db: Session = Depends(get_db)):
|
||||||
return db.query(models.User).all()
|
return db.query(models.User).all()
|
||||||
|
|
||||||
@app.post("/users", response_model=schemas.User)
|
@app.post("/users", response_model=schemas.User)
|
||||||
@@ -169,7 +174,7 @@ def restore_task(task_id: str, db: Session = Depends(get_db), current_user: mode
|
|||||||
db.refresh(db_task)
|
db.refresh(db_task)
|
||||||
return db_task
|
return db_task
|
||||||
@app.get("/workspace", response_model=schemas.Workspace)
|
@app.get("/workspace", response_model=schemas.Workspace)
|
||||||
def read_workspace(db: Session = Depends(get_db), current_user: models.User = Depends(auth.get_current_user)):
|
def read_workspace(db: Session = Depends(get_db)):
|
||||||
ws = db.query(models.Workspace).first()
|
ws = db.query(models.Workspace).first()
|
||||||
if not ws:
|
if not ws:
|
||||||
ws = models.Workspace(id="default", name="murchison-auto", timezone="Pacific/Auckland")
|
ws = models.Workspace(id="default", name="murchison-auto", timezone="Pacific/Auckland")
|
||||||
|
|||||||
+33
-38
@@ -1,20 +1,19 @@
|
|||||||
// Screens for Dashy
|
// Screens for Dashy
|
||||||
|
|
||||||
function LoginScreen({ onLogin, dbUsers = [], workspace }) {
|
function LoginScreen({ onLogin, dbUsers = [], workspace }) {
|
||||||
const [pickedId, setPickedId] = React.useState('rod');
|
const [username, setUsername] = React.useState('');
|
||||||
const [password, setPassword] = React.useState('');
|
const [password, setPassword] = React.useState('');
|
||||||
const [error, setError] = React.useState('');
|
const [error, setError] = React.useState('');
|
||||||
const [busy, setBusy] = React.useState(false);
|
const [busy, setBusy] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => { setPassword(''); setError(''); }, [pickedId]);
|
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
if (!username) { setError('Enter your username'); return; }
|
||||||
if (!password) { setError('Enter your password'); return; }
|
if (!password) { setError('Enter your password'); return; }
|
||||||
setError(''); setBusy(true);
|
setError(''); setBusy(true);
|
||||||
try {
|
try {
|
||||||
await onLogin(pickedId, password);
|
await onLogin(username, password);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError('Incorrect password');
|
setError('Incorrect username or password');
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
@@ -27,43 +26,39 @@ function LoginScreen({ onLogin, dbUsers = [], workspace }) {
|
|||||||
<BrandMark />
|
<BrandMark />
|
||||||
<span className="login__wordmark">Dashy</span>
|
<span className="login__wordmark">Dashy</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="login__title">Pick up where you left off.</h1>
|
<h1 className="login__title">Sign in to Dashy</h1>
|
||||||
<p className="login__sub">Sign in to your team workspace · <span className="mono">{workspace ? workspace.name : 'loading…'}</span></p>
|
<p className="login__sub">Enter your details to access the <span className="mono">{workspace ? workspace.name : 'loading…'}</span> workspace</p>
|
||||||
|
|
||||||
<div className="login__users">
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px', marginTop: '1rem' }}>
|
||||||
{dbUsers.map(u => (
|
<label className="field">
|
||||||
<button
|
<span className="field__label">Username</span>
|
||||||
key={u.id}
|
<input
|
||||||
className={"login__user" + (pickedId === u.id ? " is-picked" : "")}
|
className="field__input"
|
||||||
onClick={() => setPickedId(u.id)}
|
value={username}
|
||||||
>
|
onChange={e => setUsername(e.target.value)}
|
||||||
<Avatar user={u} size={40} />
|
onKeyDown={e => { if (e.key === 'Enter') submit(); }}
|
||||||
<div className="login__user-meta">
|
placeholder="Username"
|
||||||
<span className="login__user-name">{u.name}</span>
|
autoFocus
|
||||||
<span className="login__user-role">{u.role}</span>
|
/>
|
||||||
</div>
|
</label>
|
||||||
{pickedId === u.id && <span className="login__user-tick"><Icon.Check /></span>}
|
|
||||||
</button>
|
<label className="field">
|
||||||
))}
|
<span className="field__label">Password</span>
|
||||||
|
<input
|
||||||
|
className="field__input"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
onKeyDown={e => { if (e.key === 'Enter') submit(); }}
|
||||||
|
placeholder="••••••••"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="field">
|
{error && <div className="mono" style={{ color: 'var(--prio-high-dot)', marginTop: '0.25rem', fontSize: '12px' }}>{error}</div>}
|
||||||
<span className="field__label">Password</span>
|
|
||||||
<input
|
|
||||||
className="field__input"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={e => setPassword(e.target.value)}
|
|
||||||
onKeyDown={e => { if (e.key === 'Enter') submit(); }}
|
|
||||||
placeholder="Enter password"
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{error && <div className="mono" style={{ color: 'var(--prio-high-dot)', marginTop: '0.25rem' }}>{error}</div>}
|
<button className="btn btn--primary btn--full" style={{ marginTop: '0.5rem' }} onClick={submit} disabled={busy}>
|
||||||
|
{busy ? 'Signing in…' : 'Sign in'}
|
||||||
<button className="btn btn--primary btn--full" onClick={submit} disabled={busy}>
|
|
||||||
{busy ? 'Signing in…' : <>Sign in as {findUser(pickedId).name}</>}
|
|
||||||
<Icon.Arrow />
|
<Icon.Arrow />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user