refactor: replace inline styles with new CSS utility classes across various pages.
This commit is contained in:
@@ -110,6 +110,224 @@ a:hover {
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
UTILITY CLASSES
|
||||||
|
===================================================== */
|
||||||
|
|
||||||
|
/* Spacing - Margin Bottom */
|
||||||
|
.mb-xs {
|
||||||
|
margin-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-sm {
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-md {
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-lg {
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-xl {
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spacing - Margin Top */
|
||||||
|
.mt-xs {
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-sm {
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-md {
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-lg {
|
||||||
|
margin-top: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-xl {
|
||||||
|
margin-top: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spacing - Margin Left */
|
||||||
|
.ml-xs {
|
||||||
|
margin-left: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-sm {
|
||||||
|
margin-left: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-md {
|
||||||
|
margin-left: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spacing - Padding */
|
||||||
|
.p-sm {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-md {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-lg {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-xl {
|
||||||
|
padding: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flex Layouts */
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-between {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gaps */
|
||||||
|
.gap-xs {
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-sm {
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-md {
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-lg {
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text Utilities */
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-medium {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-semibold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display */
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Width */
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-w-sm {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-w-md {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-w-lg {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-w-xl {
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Min Width / Flex */
|
||||||
|
.min-w-0 {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Border */
|
||||||
|
.border-t {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
END UTILITY CLASSES
|
||||||
|
===================================================== */
|
||||||
|
|
||||||
/* Button Base */
|
/* Button Base */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -463,7 +681,7 @@ select.input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.biomarker-info {
|
.biomarker-info {
|
||||||
flex: 0 0 240px;
|
flex: 0 0 320px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -473,13 +691,11 @@ select.input {
|
|||||||
|
|
||||||
.biomarker-info .biomarker-name {
|
.biomarker-info .biomarker-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.biomarker-info .biomarker-unit {
|
.biomarker-info .biomarker-unit {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Biomarker Scale Bar */
|
/* Biomarker Scale Bar */
|
||||||
@@ -785,4 +1001,97 @@ select.input {
|
|||||||
.page-loading {
|
.page-loading {
|
||||||
padding: var(--space-lg);
|
padding: var(--space-lg);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
SOURCES PAGE
|
||||||
|
===================================================== */
|
||||||
|
|
||||||
|
/* Upload Zone */
|
||||||
|
.upload-zone {
|
||||||
|
border: 2px dashed var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background-color: color-mix(in srgb, var(--accent) 5%, var(--bg-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone.drag-over {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background-color: color-mix(in srgb, var(--accent) 5%, var(--bg-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-zone.uploading {
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upload Icon */
|
||||||
|
.upload-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Source Item */
|
||||||
|
.source-item {
|
||||||
|
padding: var(--space-sm) 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Parsed */
|
||||||
|
.status-parsed {
|
||||||
|
color: var(--indicator-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon sizes */
|
||||||
|
.icon-sm {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
DASHBOARD PAGE
|
||||||
|
===================================================== */
|
||||||
|
|
||||||
|
/* Category Card */
|
||||||
|
.category-card {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category Name */
|
||||||
|
.category-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapsible Header Button */
|
||||||
|
.collapsible-header {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collapse Icon */
|
||||||
|
.collapse-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
INSIGHTS PAGE
|
||||||
|
===================================================== */
|
||||||
|
|
||||||
|
.coming-soon-icon {
|
||||||
|
font-size: 48px;
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ export function AdminPage() {
|
|||||||
const isConfigAdmin = (user: User) => user.username === CONFIG_ADMIN_USERNAME
|
const isConfigAdmin = (user: User) => user.username === CONFIG_ADMIN_USERNAME
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div style={{ padding: 'var(--space-lg)' }}>Loading...</div>
|
return <div className="p-lg">Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentUser || currentUser.role !== 'admin') {
|
if (!currentUser || currentUser.role !== 'admin') {
|
||||||
@@ -129,10 +129,10 @@ export function AdminPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 'var(--space-lg)', maxWidth: '1000px', margin: '0 auto' }}>
|
<div className="admin-page p-lg max-w-xl">
|
||||||
<header style={{ marginBottom: 'var(--space-xl)' }}>
|
<header className="mb-xl">
|
||||||
<Link to="/" className="text-secondary text-sm">← Back to Dashboard</Link>
|
<Link to="/" className="text-secondary text-sm">← Back to Dashboard</Link>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 'var(--space-sm)' }}>
|
<div className="flex-between mt-sm">
|
||||||
<h1>User Management</h1>
|
<h1>User Management</h1>
|
||||||
<button className="btn btn-primary" onClick={() => setShowCreateModal(true)}>
|
<button className="btn btn-primary" onClick={() => setShowCreateModal(true)}>
|
||||||
+ New User
|
+ New User
|
||||||
@@ -172,7 +172,7 @@ export function AdminPage() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="text-secondary text-sm">{new Date(user.created_at).toLocaleDateString()}</td>
|
<td className="text-secondary text-sm">{new Date(user.created_at).toLocaleDateString()}</td>
|
||||||
<td>
|
<td>
|
||||||
<div style={{ display: 'flex', gap: 'var(--space-xs)' }}>
|
<div className="flex gap-xs">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary btn-sm"
|
className="btn btn-secondary btn-sm"
|
||||||
onClick={() => setEditingUser(user)}
|
onClick={() => setEditingUser(user)}
|
||||||
@@ -243,7 +243,7 @@ export function AdminPage() {
|
|||||||
<option value="admin">Admin</option>
|
<option value="admin">Admin</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 'var(--space-sm)', marginTop: 'var(--space-lg)' }}>
|
<div className="flex gap-sm mt-lg">
|
||||||
<button type="submit" className="btn btn-primary">Create</button>
|
<button type="submit" className="btn btn-primary">Create</button>
|
||||||
<button type="button" className="btn btn-secondary" onClick={() => setShowCreateModal(false)}>Cancel</button>
|
<button type="button" className="btn btn-secondary" onClick={() => setShowCreateModal(false)}>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -327,7 +327,7 @@ function EditUserModal({ user, onClose, onSave, setMessage }: EditUserModalProps
|
|||||||
placeholder="Full name"
|
placeholder="Full name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 'var(--space-sm)', marginTop: 'var(--space-lg)' }}>
|
<div className="flex gap-sm mt-lg">
|
||||||
<button type="submit" className="btn btn-primary" disabled={saving}>
|
<button type="submit" className="btn btn-primary" disabled={saving}>
|
||||||
{saving ? 'Saving...' : 'Save'}
|
{saving ? 'Saving...' : 'Save'}
|
||||||
</button>
|
</button>
|
||||||
@@ -419,7 +419,7 @@ function ResetPasswordModal({ user, onClose, onSave, setMessage }: ResetPassword
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{error && <div className="error-message">{error}</div>}
|
{error && <div className="error-message">{error}</div>}
|
||||||
<div style={{ display: 'flex', gap: 'var(--space-sm)', marginTop: 'var(--space-lg)' }}>
|
<div className="flex gap-sm mt-lg">
|
||||||
<button type="submit" className="btn btn-primary" disabled={saving}>
|
<button type="submit" className="btn btn-primary" disabled={saving}>
|
||||||
{saving ? 'Resetting...' : 'Reset Password'}
|
{saving ? 'Resetting...' : 'Reset Password'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -60,53 +60,38 @@ export function DashboardPage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 style={{ marginBottom: 'var(--space-md)' }}>Biomarker Categories</h2>
|
<h2 className="mb-md">Biomarker Categories</h2>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-sm)' }}>
|
<div className="flex-col gap-sm">
|
||||||
{categories.map(category => {
|
{categories.map(category => {
|
||||||
const categoryBiomarkers = getBiomarkersForCategory(category.id)
|
const categoryBiomarkers = getBiomarkersForCategory(category.id)
|
||||||
const isExpanded = expandedCategories.has(category.id)
|
const isExpanded = expandedCategories.has(category.id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={category.id} className="card" style={{ padding: 0 }}>
|
<div key={category.id} className="card category-card">
|
||||||
<button
|
<button
|
||||||
className="collapsible-header"
|
className="collapsible-header w-full p-md flex-between"
|
||||||
onClick={() => toggleCategory(category.id)}
|
onClick={() => toggleCategory(category.id)}
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: 'var(--space-md)',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
textAlign: 'left',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontSize: '16px', fontWeight: 600, textTransform: 'uppercase' }}>{category.name}</span>
|
<span className="category-name">{category.name}</span>
|
||||||
<span className="text-secondary text-sm" style={{ marginLeft: 'var(--space-sm)' }}>
|
<span className="text-secondary text-sm ml-sm">
|
||||||
({categoryBiomarkers.length} biomarkers)
|
({categoryBiomarkers.length} biomarkers)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
src="/icons/general/icons8-collapse-arrow-50.png"
|
src="/icons/general/icons8-collapse-arrow-50.png"
|
||||||
alt="expand"
|
alt="expand"
|
||||||
className="theme-icon"
|
className="theme-icon collapse-icon"
|
||||||
style={{
|
style={{
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
transition: 'transform 0.2s',
|
|
||||||
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0)'
|
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div style={{ borderTop: '1px solid var(--border)', padding: 'var(--space-sm)' }}>
|
<div className="category-content border-t p-sm">
|
||||||
{categoryBiomarkers.length === 0 ? (
|
{categoryBiomarkers.length === 0 ? (
|
||||||
<p className="text-secondary text-sm" style={{ padding: 'var(--space-sm)' }}>
|
<p className="text-secondary text-sm p-sm">
|
||||||
No biomarkers in this category
|
No biomarkers in this category
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export function InsightsPage() {
|
|||||||
<p className="text-secondary">AI-powered analysis of your health data</p>
|
<p className="text-secondary">AI-powered analysis of your health data</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="card" style={{ textAlign: 'center', padding: 'var(--space-xl)' }}>
|
<div className="card text-center p-xl">
|
||||||
<div style={{ fontSize: '48px', marginBottom: 'var(--space-md)' }}>🚀</div>
|
<div className="coming-soon-icon mb-md">🚀</div>
|
||||||
<h3>Coming Soon</h3>
|
<h3>Coming Soon</h3>
|
||||||
<p className="text-secondary">
|
<p className="text-secondary">
|
||||||
AI-generated health insights and recommendations based on your biomarker data.
|
AI-generated health insights and recommendations based on your biomarker data.
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function LoginPage() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p className="text-secondary text-sm" style={{ marginTop: 'var(--space-md)', textAlign: 'center' }}>
|
<p className="text-secondary text-sm mt-md text-center">
|
||||||
Don't have an account? <Link to="/signup">Sign up</Link>
|
Don't have an account? <Link to="/signup">Sign up</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export function ProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page" style={{ maxWidth: '600px' }}>
|
<div className="page max-w-md">
|
||||||
<header className="page-header">
|
<header className="page-header">
|
||||||
<h1>Profile</h1>
|
<h1>Profile</h1>
|
||||||
<p className="text-secondary">Manage your account and health information</p>
|
<p className="text-secondary">Manage your account and health information</p>
|
||||||
@@ -131,8 +131,8 @@ export function ProfilePage() {
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{/* Account Info */}
|
{/* Account Info */}
|
||||||
<div className="card" style={{ marginBottom: 'var(--space-lg)' }}>
|
<div className="card mb-lg">
|
||||||
<h3 style={{ marginBottom: 'var(--space-md)' }}>Account</h3>
|
<h3 className="mb-md">Account</h3>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
<input type="text" className="input" value={username} disabled />
|
<input type="text" className="input" value={username} disabled />
|
||||||
@@ -165,8 +165,8 @@ export function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Physical Info */}
|
{/* Physical Info */}
|
||||||
<div className="card" style={{ marginBottom: 'var(--space-lg)' }}>
|
<div className="card mb-lg">
|
||||||
<h3 style={{ marginBottom: 'var(--space-md)' }}>Physical Info</h3>
|
<h3 className="mb-md">Physical Info</h3>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="height">Height (cm)</label>
|
<label htmlFor="height">Height (cm)</label>
|
||||||
@@ -214,8 +214,8 @@ export function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lifestyle */}
|
{/* Lifestyle */}
|
||||||
<div className="card" style={{ marginBottom: 'var(--space-lg)' }}>
|
<div className="card mb-lg">
|
||||||
<h3 style={{ marginBottom: 'var(--space-md)' }}>Lifestyle</h3>
|
<h3 className="mb-md">Lifestyle</h3>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="diet">Diet</label>
|
<label htmlFor="diet">Diet</label>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function SignupPage() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p className="text-secondary text-sm" style={{ marginTop: 'var(--space-md)', textAlign: 'center' }}>
|
<p className="text-secondary text-sm mt-md text-center">
|
||||||
Already have an account? <Link to="/login">Sign in</Link>
|
Already have an account? <Link to="/login">Sign in</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -117,13 +117,13 @@ export function SourcesPage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h3 style={{ marginBottom: 'var(--space-md)' }}>Upload Data</h3>
|
<h3 className="mb-md">Upload Data</h3>
|
||||||
<p className="text-secondary text-sm" style={{ marginBottom: 'var(--space-lg)' }}>
|
<p className="text-secondary text-sm mb-lg">
|
||||||
Upload lab reports in PDF, CSV, or Excel format to import your biomarker data.
|
Upload lab reports in PDF, CSV, or Excel format to import your biomarker data.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="alert alert-error" style={{ marginBottom: 'var(--space-md)' }}>
|
<div className="alert alert-error mb-md">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -131,7 +131,7 @@ export function SourcesPage() {
|
|||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
style={{ display: 'none' }}
|
className="hidden"
|
||||||
multiple
|
multiple
|
||||||
accept=".pdf,.csv,.xlsx,.xls,.jpg,.jpeg,.png"
|
accept=".pdf,.csv,.xlsx,.xls,.jpg,.jpeg,.png"
|
||||||
onChange={(e) => handleUpload(e.target.files)}
|
onChange={(e) => handleUpload(e.target.files)}
|
||||||
@@ -139,15 +139,6 @@ export function SourcesPage() {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={`upload-zone ${dragOver ? 'drag-over' : ''}`}
|
className={`upload-zone ${dragOver ? 'drag-over' : ''}`}
|
||||||
style={{
|
|
||||||
border: `2px dashed ${dragOver ? 'var(--accent)' : 'var(--border)'}`,
|
|
||||||
borderRadius: 'var(--radius-md)',
|
|
||||||
padding: 'var(--space-xl)',
|
|
||||||
textAlign: 'center',
|
|
||||||
cursor: uploading ? 'wait' : 'pointer',
|
|
||||||
backgroundColor: dragOver ? 'color-mix(in srgb, var(--accent) 5%, var(--bg-secondary))' : 'transparent',
|
|
||||||
transition: 'all 0.2s',
|
|
||||||
}}
|
|
||||||
onClick={() => !uploading && fileInputRef.current?.click()}
|
onClick={() => !uploading && fileInputRef.current?.click()}
|
||||||
onDragOver={(e) => { e.preventDefault(); setDragOver(true) }}
|
onDragOver={(e) => { e.preventDefault(); setDragOver(true) }}
|
||||||
onDragLeave={() => setDragOver(false)}
|
onDragLeave={() => setDragOver(false)}
|
||||||
@@ -159,20 +150,20 @@ export function SourcesPage() {
|
|||||||
>
|
>
|
||||||
{uploading ? (
|
{uploading ? (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: 'var(--space-sm)', textAlign: 'center' }}>
|
<div className="mb-sm text-center">
|
||||||
<img src="/icons/general/icons8-clock-50.png" alt="Uploading" className="theme-icon" style={{ width: 36, height: 36, display: 'block', margin: '0 auto' }} />
|
<img src="/icons/general/icons8-clock-50.png" alt="Uploading" className="upload-icon theme-icon" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-secondary">Uploading...</p>
|
<p className="text-secondary">Uploading...</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: 'var(--space-sm)', textAlign: 'center' }}>
|
<div className="mb-sm text-center">
|
||||||
<img src="/icons/general/icons8-upload-to-the-cloud-50.png" alt="Upload" className="theme-icon" style={{ width: 36, height: 36, display: 'block', margin: '0 auto' }} />
|
<img src="/icons/general/icons8-upload-to-the-cloud-50.png" alt="Upload" className="upload-icon theme-icon" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-secondary">
|
<p className="text-secondary">
|
||||||
Drag & drop files here, or click to browse
|
Drag & drop files here, or click to browse
|
||||||
</p>
|
</p>
|
||||||
<p className="text-secondary text-xs" style={{ marginTop: 'var(--space-sm)' }}>
|
<p className="text-secondary text-xs mt-sm">
|
||||||
Supported: PDF, CSV, XLSX, Images
|
Supported: PDF, CSV, XLSX, Images
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
@@ -180,8 +171,8 @@ export function SourcesPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card" style={{ marginTop: 'var(--space-lg)' }}>
|
<div className="card mt-lg">
|
||||||
<h3 style={{ marginBottom: 'var(--space-md)' }}>Recent Uploads</h3>
|
<h3 className="mb-md">Recent Uploads</h3>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-secondary text-sm">Loading...</p>
|
<p className="text-secondary text-sm">Loading...</p>
|
||||||
@@ -190,28 +181,22 @@ export function SourcesPage() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="sources-list">
|
<div className="sources-list">
|
||||||
{sources.map(source => (
|
{sources.map(source => (
|
||||||
<div key={source.id} className="source-item" style={{
|
<div key={source.id} className="source-item flex-between">
|
||||||
display: 'flex',
|
<div className="flex-1 min-w-0">
|
||||||
alignItems: 'center',
|
<div className="font-medium truncate">
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: 'var(--space-sm) 0',
|
|
||||||
borderBottom: '1px solid var(--border)',
|
|
||||||
}}>
|
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
|
||||||
<div style={{ fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
||||||
{source.name}
|
{source.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-secondary text-xs">
|
<div className="text-secondary text-xs">
|
||||||
{source.file_type} • {formatFileSize(source.file_size)} • {formatDate(source.uploaded_at)}
|
{source.file_type} • {formatFileSize(source.file_size)} • {formatDate(source.uploaded_at)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 'var(--space-sm)' }}>
|
<div className="flex gap-sm items-center">
|
||||||
{source.ocr_data ? (
|
{source.ocr_data ? (
|
||||||
<span style={{ color: 'var(--success)', fontSize: '12px', display: 'flex', alignItems: 'center', gap: '4px' }}>
|
<span className="status-parsed flex items-center gap-xs text-xs">
|
||||||
<img src="/icons/general/icons8-checkmark-50.png" alt="Parsed" style={{ width: 14, height: 14 }} /> Parsed
|
<img src="/icons/general/icons8-checkmark-50.png" alt="Parsed" className="icon-sm" /> Parsed
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span style={{ color: 'var(--text-secondary)', fontSize: '12px' }}>Pending</span>
|
<span className="text-secondary text-xs">Pending</span>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="btn btn-danger btn-sm"
|
className="btn btn-danger btn-sm"
|
||||||
@@ -228,21 +213,13 @@ export function SourcesPage() {
|
|||||||
|
|
||||||
{/* Delete Confirmation Modal */}
|
{/* Delete Confirmation Modal */}
|
||||||
{deleteConfirmId !== null && (
|
{deleteConfirmId !== null && (
|
||||||
<div style={{
|
<div className="modal-overlay">
|
||||||
position: 'fixed',
|
<div className="card modal-content">
|
||||||
inset: 0,
|
<h3 className="mb-md">Delete File?</h3>
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
<p className="text-secondary mb-lg">
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
zIndex: 1000,
|
|
||||||
}}>
|
|
||||||
<div className="card" style={{ maxWidth: 400, textAlign: 'center' }}>
|
|
||||||
<h3 style={{ marginBottom: 'var(--space-md)' }}>Delete File?</h3>
|
|
||||||
<p className="text-secondary" style={{ marginBottom: 'var(--space-lg)' }}>
|
|
||||||
Are you sure you want to delete this file? This action cannot be undone.
|
Are you sure you want to delete this file? This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'flex', gap: 'var(--space-sm)', justifyContent: 'center' }}>
|
<div className="flex gap-sm justify-center">
|
||||||
<button className="btn" onClick={() => setDeleteConfirmId(null)}>
|
<button className="btn" onClick={() => setDeleteConfirmId(null)}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user