v0.11.13 login screen fix
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jama-backend",
|
"name": "jama-backend",
|
||||||
"version": "0.11.12",
|
"version": "0.11.13",
|
||||||
"description": "TeamChat backend server",
|
"description": "TeamChat backend server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${1:-0.11.12}"
|
VERSION="${1:-0.11.13}"
|
||||||
ACTION="${2:-}"
|
ACTION="${2:-}"
|
||||||
REGISTRY="${REGISTRY:-}"
|
REGISTRY="${REGISTRY:-}"
|
||||||
IMAGE_NAME="jama"
|
IMAGE_NAME="jama"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jama-frontend",
|
"name": "jama-frontend",
|
||||||
"version": "0.11.12",
|
"version": "0.11.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
57
frontend/src/components/PasswordInput.jsx
Normal file
57
frontend/src/components/PasswordInput.jsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PasswordInput — a password field with an inline show/hide toggle.
|
||||||
|
* Accepts all standard <input> props plus className/style on the wrapper.
|
||||||
|
* Usage:
|
||||||
|
* <PasswordInput value={pw} onChange={e => setPw(e.target.value)} />
|
||||||
|
*/
|
||||||
|
export default function PasswordInput({ className, style, wrapperStyle, ...inputProps }) {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative', display: 'flex', alignItems: 'center', ...wrapperStyle }}>
|
||||||
|
<input
|
||||||
|
{...inputProps}
|
||||||
|
type={show ? 'text' : 'password'}
|
||||||
|
className={className ?? 'input'}
|
||||||
|
style={{ paddingRight: 38, width: '100%', ...style }}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShow(s => !s)}
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={show ? 'Hide password' : 'Show password'}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 10,
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: 2,
|
||||||
|
color: 'var(--text-tertiary)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{show ? (
|
||||||
|
/* Eye-off icon */
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>
|
||||||
|
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23"/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
/* Eye icon */
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||||
|
<circle cx="12" cy="12" r="3"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useAuth } from '../contexts/AuthContext.jsx';
|
import { useAuth } from '../contexts/AuthContext.jsx';
|
||||||
import { useToast } from '../contexts/ToastContext.jsx';
|
import { useToast } from '../contexts/ToastContext.jsx';
|
||||||
import { api } from '../utils/api.js';
|
import { api } from '../utils/api.js';
|
||||||
|
import PasswordInput from '../components/PasswordInput.jsx';
|
||||||
|
|
||||||
export default function ChangePassword() {
|
export default function ChangePassword() {
|
||||||
const [current, setCurrent] = useState('');
|
const [current, setCurrent] = useState('');
|
||||||
@@ -40,15 +41,15 @@ export default function ChangePassword() {
|
|||||||
<form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
<div className="flex-col gap-1">
|
<div className="flex-col gap-1">
|
||||||
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>Current Password</label>
|
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>Current Password</label>
|
||||||
<input className="input" type="password" value={current} onChange={e => setCurrent(e.target.value)} autoComplete="new-password" required />
|
<PasswordInput value={current} onChange={e => setCurrent(e.target.value)} autoComplete="new-password" required />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-col gap-1">
|
<div className="flex-col gap-1">
|
||||||
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>New Password</label>
|
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>New Password</label>
|
||||||
<input className="input" type="password" value={next} onChange={e => setNext(e.target.value)} autoComplete="new-password" required />
|
<PasswordInput value={next} onChange={e => setNext(e.target.value)} autoComplete="new-password" required />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-col gap-1">
|
<div className="flex-col gap-1">
|
||||||
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>Confirm New Password</label>
|
<label className="text-sm font-medium" style={{ color: 'var(--text-secondary)' }}>Confirm New Password</label>
|
||||||
<input className="input" type="password" value={confirm} onChange={e => setConfirm(e.target.value)} autoComplete="new-password" required />
|
<PasswordInput value={confirm} onChange={e => setConfirm(e.target.value)} autoComplete="new-password" required />
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" type="submit" disabled={loading}>
|
<button className="btn btn-primary" type="submit" disabled={loading}>
|
||||||
{loading ? 'Saving...' : 'Set New Password'}
|
{loading ? 'Saving...' : 'Set New Password'}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.login-page {
|
.login-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh; /* fallback for browsers without dvh support */
|
||||||
|
min-height: 100dvh; /* dynamic viewport height — prevents bounce when mobile browser chrome resizes */
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useToast } from '../contexts/ToastContext.jsx';
|
|||||||
import { api } from '../utils/api.js';
|
import { api } from '../utils/api.js';
|
||||||
import './Login.css';
|
import './Login.css';
|
||||||
import SupportModal from '../components/SupportModal.jsx';
|
import SupportModal from '../components/SupportModal.jsx';
|
||||||
|
import PasswordInput from '../components/PasswordInput.jsx';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@@ -96,7 +97,7 @@ export default function Login() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
<input className="input" type="password" value={password} onChange={e => setPassword(e.target.value)} required placeholder="••••••••" />
|
<PasswordInput value={password} onChange={e => setPassword(e.target.value)} required placeholder="••••••••" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="remember-me">
|
<label className="remember-me">
|
||||||
|
|||||||
Reference in New Issue
Block a user