feat(web): accueil et profil — allègement du hero (textes descriptifs retirés), grilles de livres pleine largeur à 5 colonnes (auto-fit sans max-width), liste des bibliothèques scrollable (3 lignes visibles, partagée avec .continue-grid), et écran de profil clarifié (avatar, bloc identité, actions Modifier/Sortir — évolution #48)
This commit is contained in:
@ -51,9 +51,7 @@ export function HomePage() {
|
|||||||
<div className="page-grid home-page">
|
<div className="page-grid home-page">
|
||||||
<section className="hero-band home-hero">
|
<section className="hero-band home-hero">
|
||||||
<div>
|
<div>
|
||||||
<p>Bibliotheque personnelle</p>
|
|
||||||
<h1>Reprendre la lecture.</h1>
|
<h1>Reprendre la lecture.</h1>
|
||||||
<span>Catalogue branche sur le serveur local.</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button className="primary-button" onClick={() => navigate("/search")}>
|
<button className="primary-button" onClick={() => navigate("/search")}>
|
||||||
<ScanLine size={18} />
|
<ScanLine size={18} />
|
||||||
|
|||||||
@ -4,14 +4,13 @@ import { describe, expect, it } from "vitest";
|
|||||||
describe("home progress list layout", () => {
|
describe("home progress list layout", () => {
|
||||||
it("keeps the continue reading list scrollable after three visible books", () => {
|
it("keeps the continue reading list scrollable after three visible books", () => {
|
||||||
const styles = readFileSync(new URL("../styles/app.css", import.meta.url), "utf8");
|
const styles = readFileSync(new URL("../styles/app.css", import.meta.url), "utf8");
|
||||||
const continueGrid = styles.match(/\.continue-grid\s*\{[^}]+\}/)?.[0] ?? "";
|
|
||||||
const continueTile = styles.match(/\.continue-tile\s*\{[^}]+\}/)?.[0] ?? "";
|
const continueTile = styles.match(/\.continue-tile\s*\{[^}]+\}/)?.[0] ?? "";
|
||||||
|
|
||||||
expect(continueGrid).toContain("--continue-visible-rows: 3");
|
expect(styles).toContain(".continue-grid,\n.library-list {\n --continue-visible-rows: 3;");
|
||||||
expect(continueGrid).toContain("--continue-tile-block-size: 112px");
|
expect(styles).toContain("--continue-tile-block-size: 112px");
|
||||||
expect(continueGrid).toContain("max-height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)))");
|
expect(styles).toContain("max-height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)))");
|
||||||
expect(continueGrid).toContain("overflow-y: auto");
|
expect(styles).toContain("overflow-y: auto");
|
||||||
expect(continueGrid).toContain("scrollbar-gutter: stable");
|
expect(styles).toContain("scrollbar-gutter: stable");
|
||||||
expect(continueTile).toContain("block-size: var(--continue-tile-block-size)");
|
expect(continueTile).toContain("block-size: var(--continue-tile-block-size)");
|
||||||
expect(continueTile).toContain("overflow: hidden");
|
expect(continueTile).toContain("overflow: hidden");
|
||||||
});
|
});
|
||||||
@ -33,6 +32,8 @@ describe("home progress list layout", () => {
|
|||||||
expect(source).not.toContain("book.language");
|
expect(source).not.toContain("book.language");
|
||||||
expect(source).not.toContain("API absente ou incomplete");
|
expect(source).not.toContain("API absente ou incomplete");
|
||||||
expect(source).not.toContain("specimens de demonstration actifs");
|
expect(source).not.toContain("specimens de demonstration actifs");
|
||||||
|
expect(source).not.toContain("Catalogue branche sur le serveur local");
|
||||||
|
expect(source).not.toContain("Bibliotheque personnelle");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps visible book grids capped to five columns", () => {
|
it("keeps visible book grids capped to five columns", () => {
|
||||||
@ -40,7 +41,22 @@ describe("home progress list layout", () => {
|
|||||||
const bookGrid = styles.match(/\.book-grid\s*\{[^}]+\}/)?.[0] ?? "";
|
const bookGrid = styles.match(/\.book-grid\s*\{[^}]+\}/)?.[0] ?? "";
|
||||||
const homeBookGrid = styles.match(/\.home-book-grid\s*\{[^}]+\}/)?.[0] ?? "";
|
const homeBookGrid = styles.match(/\.home-book-grid\s*\{[^}]+\}/)?.[0] ?? "";
|
||||||
|
|
||||||
expect(bookGrid).toContain("max-width: calc((var(--book-grid-column-min) * 5) + (var(--book-grid-gap) * 4))");
|
expect(bookGrid).toContain("grid-template-columns: repeat(auto-fit, minmax(min(100%, max(var(--book-grid-column-min), calc((100% - (var(--book-grid-gap) * 4)) / 5))), 1fr))");
|
||||||
expect(homeBookGrid).toContain("max-width: calc((var(--book-grid-column-min) * 5) + (var(--book-grid-gap) * 4))");
|
expect(homeBookGrid).toContain("grid-template-columns: repeat(auto-fit, minmax(min(100%, max(var(--book-grid-column-min), calc((100% - (var(--book-grid-gap) * 4)) / 5))), 1fr))");
|
||||||
|
expect(bookGrid).toContain("width: 100%");
|
||||||
|
expect(homeBookGrid).toContain("width: 100%");
|
||||||
|
expect(bookGrid).not.toContain("max-width");
|
||||||
|
expect(homeBookGrid).not.toContain("max-width");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the library list capped like continue reading", () => {
|
||||||
|
const styles = readFileSync(new URL("../styles/app.css", import.meta.url), "utf8");
|
||||||
|
const libraryButton = styles.match(/\.library-list button\s*\{[^}]+\}/)?.[0] ?? "";
|
||||||
|
|
||||||
|
expect(styles).toContain(".continue-grid,\n.library-list {\n --continue-visible-rows: 3;");
|
||||||
|
expect(styles).toContain("height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)))");
|
||||||
|
expect(styles).toContain("max-height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)))");
|
||||||
|
expect(libraryButton).toContain("block-size: var(--continue-tile-block-size)");
|
||||||
|
expect(libraryButton).toContain("overflow: hidden");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -42,14 +42,24 @@ export function ProfilePage({ session, onSessionChange }: { session: Session; on
|
|||||||
return (
|
return (
|
||||||
<div className="page-grid">
|
<div className="page-grid">
|
||||||
<Panel className="profile-panel">
|
<Panel className="profile-panel">
|
||||||
|
<div className="profile-identity">
|
||||||
|
<span className="profile-avatar" aria-hidden="true">
|
||||||
<UserRound size={28} />
|
<UserRound size={28} />
|
||||||
|
</span>
|
||||||
<h1>{session.user?.name ?? "Lecteur invite"}</h1>
|
<h1>{session.user?.name ?? "Lecteur invite"}</h1>
|
||||||
<p>{session.user?.email ?? "Session non connectee"}</p>
|
<p>{session.user?.email ?? "Session non connectee"}</p>
|
||||||
<span>{session.user?.role ?? "vitrine"}</span>
|
<span>{session.user?.role ?? "vitrine"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="profile-actions">
|
||||||
|
<button className="ghost-button" onClick={() => document.getElementById("profile-security-form")?.scrollIntoView({ block: "start" })}>
|
||||||
|
<KeyRound size={17} />
|
||||||
|
Modifier
|
||||||
|
</button>
|
||||||
<button className="ghost-button" onClick={logout}>
|
<button className="ghost-button" onClick={logout}>
|
||||||
<LogOut size={17} />
|
<LogOut size={17} />
|
||||||
Sortir
|
Sortir
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel className="span-2">
|
<Panel className="span-2">
|
||||||
<div className="section-heading">
|
<div className="section-heading">
|
||||||
@ -59,7 +69,7 @@ export function ProfilePage({ session, onSessionChange }: { session: Session; on
|
|||||||
<p className="muted-copy">Change l'email et le mot de passe admin initial des que le cabinet est installe.</p>
|
<p className="muted-copy">Change l'email et le mot de passe admin initial des que le cabinet est installe.</p>
|
||||||
<ErrorRibbon message={error} />
|
<ErrorRibbon message={error} />
|
||||||
{success && <div className="success-ribbon">{success}</div>}
|
{success && <div className="success-ribbon">{success}</div>}
|
||||||
<form className="stack-form" onSubmit={updateSecurity}>
|
<form id="profile-security-form" className="stack-form" onSubmit={updateSecurity}>
|
||||||
<label>
|
<label>
|
||||||
Email
|
Email
|
||||||
<input value={email} onChange={(event) => setEmail(event.target.value)} type="email" required />
|
<input value={email} onChange={(event) => setEmail(event.target.value)} type="email" required />
|
||||||
|
|||||||
17
apps/web/src/pages/ProfilePagePolish.test.ts
Normal file
17
apps/web/src/pages/ProfilePagePolish.test.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("profile page polish", () => {
|
||||||
|
it("focuses the left column on identity and immediate actions", () => {
|
||||||
|
const source = readFileSync(new URL("./ProfilePage.tsx", import.meta.url), "utf8");
|
||||||
|
const styles = readFileSync(new URL("../styles/app.css", import.meta.url), "utf8");
|
||||||
|
|
||||||
|
expect(source).toContain("profile-identity");
|
||||||
|
expect(source).toContain("profile-avatar");
|
||||||
|
expect(source).toContain("profile-actions");
|
||||||
|
expect(source).toContain('id="profile-security-form"');
|
||||||
|
expect(source).toContain("scrollIntoView");
|
||||||
|
expect(styles).toContain(".profile-identity");
|
||||||
|
expect(styles).toContain(".profile-actions");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -164,10 +164,9 @@ h2 {
|
|||||||
--book-grid-column-min: 240px;
|
--book-grid-column-min: 240px;
|
||||||
--book-grid-gap: 16px;
|
--book-grid-gap: 16px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(var(--book-grid-column-min), 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, max(var(--book-grid-column-min), calc((100% - (var(--book-grid-gap) * 4)) / 5))), 1fr));
|
||||||
gap: var(--book-grid-gap);
|
gap: var(--book-grid-gap);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: calc((var(--book-grid-column-min) * 5) + (var(--book-grid-gap) * 4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-card {
|
.book-card {
|
||||||
@ -394,9 +393,11 @@ h2 {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.continue-grid {
|
.continue-grid,
|
||||||
|
.library-list {
|
||||||
--continue-visible-rows: 3;
|
--continue-visible-rows: 3;
|
||||||
--continue-tile-block-size: 112px;
|
--continue-tile-block-size: 112px;
|
||||||
|
height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)));
|
||||||
max-height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)));
|
max-height: calc((var(--continue-tile-block-size) * var(--continue-visible-rows)) + (10px * (var(--continue-visible-rows) - 1)));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
@ -451,14 +452,18 @@ h2 {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.library-list button {
|
||||||
|
block-size: var(--continue-tile-block-size);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.home-book-grid {
|
.home-book-grid {
|
||||||
--book-grid-column-min: 190px;
|
--book-grid-column-min: 190px;
|
||||||
--book-grid-gap: 14px;
|
--book-grid-gap: 14px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(var(--book-grid-column-min), 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, max(var(--book-grid-column-min), calc((100% - (var(--book-grid-gap) * 4)) / 5))), 1fr));
|
||||||
gap: var(--book-grid-gap);
|
gap: var(--book-grid-gap);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: calc((var(--book-grid-column-min) * 5) + (var(--book-grid-gap) * 4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-book-card {
|
.home-book-card {
|
||||||
@ -980,8 +985,54 @@ select {
|
|||||||
|
|
||||||
.profile-panel {
|
.profile-panel {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
align-content: start;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-identity {
|
||||||
|
display: grid;
|
||||||
justify-items: start;
|
justify-items: start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border: 1px solid rgba(247, 240, 223, 0.14);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.045);
|
||||||
|
color: var(--brass);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-identity h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-identity p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-identity span:not(.profile-avatar) {
|
||||||
|
width: max-content;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 4px 7px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
font-size: 0.76rem;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reader-page {
|
.reader-page {
|
||||||
|
|||||||
Reference in New Issue
Block a user