fix(layout): masque l'onglet « Paramètres d'application » hors de l'app native (#107) #119
9 changed files with 174 additions and 137 deletions
|
|
@ -1,16 +1,14 @@
|
||||||
// Mocks the API via cy.intercept — this job doesn't run a live backend (see
|
// Mocks the API via cy.intercept — this job doesn't run a live backend (see
|
||||||
// .github/workflows/ci.yml); apps/api's own Mocha suite covers real API
|
// .github/workflows/ci.yml).
|
||||||
// behavior against a real database.
|
|
||||||
//
|
//
|
||||||
// Cypress always runs as a plain web browser — `Capacitor.isNativePlatform()`
|
// Issue #107 — "Paramètres d'application" is packaged-app only now. Its
|
||||||
// is never true here, so only this page's web-side content (the build
|
// content (installed version + update check) is inert on the web, and the
|
||||||
// version + the "stay on web" preference) is reachable from this spec. The
|
// one web-relevant control it carried (the "stay on web" redirect toggle)
|
||||||
// native-only half (App.getInfo()/checkNow's actual version-comparison
|
// moved to `/telecharger` (see download-page.cy.ts). Cypress always runs as
|
||||||
// network call) has no seam to fake native from a web run — same
|
// a plain web browser (`Capacitor.isNativePlatform()` is never true here),
|
||||||
// untested-on-purpose posture already accepted for `useAppUpdateCheck`'s
|
// so the page itself has no seam to reach — same untested-on-purpose
|
||||||
// background check and `UpdateBanner` (neither has e2e coverage either);
|
// posture already accepted for `useAppUpdateCheck` / `UpdateBanner`. This
|
||||||
// the underlying comparison logic itself is unit-tested in
|
// spec now only covers the web-side gating.
|
||||||
// version-compare.cy.tsx, unchanged by this ticket.
|
|
||||||
|
|
||||||
const authenticatedProfile = {
|
const authenticatedProfile = {
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
@ -22,47 +20,21 @@ const authenticatedProfile = {
|
||||||
dietId: null,
|
dietId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const STAY_ON_WEB_KEY = "batchcooking:stayOnWeb";
|
describe("App settings — hidden on the web (#107)", () => {
|
||||||
|
|
||||||
describe("App settings (/parametres/application)", () => {
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||||
|
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the web build version, and the update-check section only on native", () => {
|
it("is absent from the Paramètres menu", () => {
|
||||||
|
cy.visit("/");
|
||||||
|
cy.contains("button", "Paramètres").click();
|
||||||
|
cy.contains("a", "Paramètres d'application").should("not.exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("redirects /parametres/application to /parametres/compte instead of an orphaned page", () => {
|
||||||
cy.visit("/parametres/application");
|
cy.visit("/parametres/application");
|
||||||
|
cy.url().should("include", "/parametres/compte");
|
||||||
cy.contains("h1", "Paramètres d'application").should("be.visible");
|
cy.contains("h1", "Paramètres d'application").should("not.exist");
|
||||||
// The exact build number isn't asserted (would couple this test to
|
|
||||||
// package.json's version, bumped independently of this page) — just
|
|
||||||
// that a "Version X" line rendered at all, proving `useInstalledVersion`
|
|
||||||
// resolved something rather than being stuck empty.
|
|
||||||
cy.contains(".app-settings__version", "Version").should("be.visible");
|
|
||||||
|
|
||||||
cy.contains("button", "Vérifier les mises à jour").should("not.exist");
|
|
||||||
cy.contains(
|
|
||||||
"La vérification des mises à jour n'est disponible que dans l'application mobile",
|
|
||||||
).should("be.visible");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("defaults 'stay on web' to unchecked, and persists a toggle across a reload", () => {
|
|
||||||
cy.visit("/parametres/application");
|
|
||||||
|
|
||||||
cy.contains("label", "Toujours rester sur le site web")
|
|
||||||
.find('input[type="checkbox"]')
|
|
||||||
.should("not.be.checked");
|
|
||||||
|
|
||||||
cy.contains("label", "Toujours rester sur le site web").click();
|
|
||||||
cy.contains("label", "Toujours rester sur le site web")
|
|
||||||
.find('input[type="checkbox"]')
|
|
||||||
.should("be.checked");
|
|
||||||
cy.window().then((win) => {
|
|
||||||
expect(win.localStorage.getItem(STAY_ON_WEB_KEY)).to.equal("true");
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.reload();
|
|
||||||
cy.contains("label", "Toujours rester sur le site web")
|
|
||||||
.find('input[type="checkbox"]')
|
|
||||||
.should("be.checked");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,29 @@ describe("DownloadPage — the mobile-app-redirect fallback", () => {
|
||||||
cy.visit("/telecharger");
|
cy.visit("/telecharger");
|
||||||
cy.url().should("include", "/telecharger");
|
cy.url().should("include", "/telecharger");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Issue #107 — the "toujours rester sur le site web" preference moved
|
||||||
|
// here from "Paramètres d'application" (packaged-app only now). Written to
|
||||||
|
// `localStorage` on the public web origin, which is where
|
||||||
|
// `attemptOpenNativeApp` reads it.
|
||||||
|
it("defaults 'stay on web' to unchecked, and persists a toggle across a reload", () => {
|
||||||
|
cy.visit("/telecharger");
|
||||||
|
|
||||||
|
cy.contains("label", "Toujours rester sur le site web")
|
||||||
|
.find('input[type="checkbox"]')
|
||||||
|
.should("not.be.checked");
|
||||||
|
|
||||||
|
cy.contains("label", "Toujours rester sur le site web").click();
|
||||||
|
cy.contains("label", "Toujours rester sur le site web")
|
||||||
|
.find('input[type="checkbox"]')
|
||||||
|
.should("be.checked");
|
||||||
|
cy.window().then((win) => {
|
||||||
|
expect(win.localStorage.getItem("batchcooking:stayOnWeb")).to.equal("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.reload();
|
||||||
|
cy.contains("label", "Toujours rester sur le site web")
|
||||||
|
.find('input[type="checkbox"]')
|
||||||
|
.should("be.checked");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ describe("Sidebar — settings menu and account menu", () => {
|
||||||
cy.get(".app-sidebar__nav a").should("not.contain", "Foyer");
|
cy.get(".app-sidebar__nav a").should("not.contain", "Foyer");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reveals the four settings pages behind the Paramètres toggle", () => {
|
it("reveals the settings pages behind the Paramètres toggle", () => {
|
||||||
cy.visit("/");
|
cy.visit("/");
|
||||||
|
|
||||||
cy.contains("a", "Compte").should("not.exist");
|
cy.contains("a", "Compte").should("not.exist");
|
||||||
|
|
@ -43,11 +43,9 @@ describe("Sidebar — settings menu and account menu", () => {
|
||||||
"href",
|
"href",
|
||||||
"/parametres/preferences-utilisateur",
|
"/parametres/preferences-utilisateur",
|
||||||
);
|
);
|
||||||
cy.contains("a", "Paramètres d'application").should(
|
// "Paramètres d'application" is packaged-app only (issue #107) — Cypress
|
||||||
"have.attr",
|
// runs as a plain web browser, so it's filtered out here.
|
||||||
"href",
|
cy.contains("a", "Paramètres d'application").should("not.exist");
|
||||||
"/parametres/application",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opens the account menu from the greeting and links to Mon compte", () => {
|
it("opens the account menu from the greeting and links to Mon compte", () => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Capacitor } from "@capacitor/core";
|
||||||
import { Navigate, Outlet, Route, Routes } from "react-router-dom";
|
import { Navigate, Outlet, Route, Routes } from "react-router-dom";
|
||||||
import { AdminAuthProvider } from "./features/admin/AdminAuthContext";
|
import { AdminAuthProvider } from "./features/admin/AdminAuthContext";
|
||||||
import { RequireAdmin } from "./features/admin/RequireAdmin";
|
import { RequireAdmin } from "./features/admin/RequireAdmin";
|
||||||
|
|
@ -108,7 +109,21 @@ export function App() {
|
||||||
<Route path="/parametres/preferences" element={<PreferencesPage />} />
|
<Route path="/parametres/preferences" element={<PreferencesPage />} />
|
||||||
<Route path="/parametres/foyer" element={<HouseholdSettingsPage />} />
|
<Route path="/parametres/foyer" element={<HouseholdSettingsPage />} />
|
||||||
<Route path="/parametres/preferences-utilisateur" element={<UserPreferencesPage />} />
|
<Route path="/parametres/preferences-utilisateur" element={<UserPreferencesPage />} />
|
||||||
<Route path="/parametres/application" element={<AppSettingsPage />} />
|
{/* Native app only (issue #107): on the web its sections are inert
|
||||||
|
(no "installed version", the update check is a no-op) or have
|
||||||
|
moved to `/telecharger` (the stay-on-web toggle), and the nav
|
||||||
|
entry is hidden — so a stray bookmark redirects rather than
|
||||||
|
landing on an orphaned page (same pattern as `/foyer` below). */}
|
||||||
|
<Route
|
||||||
|
path="/parametres/application"
|
||||||
|
element={
|
||||||
|
Capacitor.isNativePlatform() ? (
|
||||||
|
<AppSettingsPage />
|
||||||
|
) : (
|
||||||
|
<Navigate to="/parametres/compte" replace />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path="/parametres/credits" element={<CreditsPage />} />
|
<Route path="/parametres/credits" element={<CreditsPage />} />
|
||||||
<Route path="/foyer" element={<Navigate to="/parametres/foyer" replace />} />
|
<Route path="/foyer" element={<Navigate to="/parametres/foyer" replace />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Capacitor } from "@capacitor/core";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
@ -41,19 +43,44 @@ const NAV_ITEMS = [
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
/** One entry in the "Paramètres" nav — see {@link SETTINGS_ITEMS}. */
|
||||||
|
interface SettingsNavItem {
|
||||||
|
to: string;
|
||||||
|
/** Maps to `layout.settings.nav.<key>` in `locales/fr/translation.json`. */
|
||||||
|
key: string;
|
||||||
|
Icon: LucideIcon;
|
||||||
/**
|
/**
|
||||||
* One entry in the "Paramètres" nav — revealed by {@link SettingsMenu} on
|
* Hidden entirely outside the packaged app (issue #107): its page's
|
||||||
* desktop, listed inside {@link MobileMoreSheet} on mobile. `key` maps to
|
* options — the version/update check, and the "stay on the website"
|
||||||
* `layout.settings.nav.<key>`.
|
* redirect toggle — are either inert on the web or have moved to
|
||||||
|
* `/telecharger`. The route itself redirects away on the web too (see
|
||||||
|
* `App.tsx`).
|
||||||
*/
|
*/
|
||||||
const SETTINGS_ITEMS = [
|
nativeOnly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "Paramètres" nav entries — revealed by {@link SettingsMenu} on
|
||||||
|
* desktop, listed inside {@link MobileMoreSheet} on mobile. Filtered to
|
||||||
|
* {@link settingsNavItems} for actual rendering.
|
||||||
|
*/
|
||||||
|
const SETTINGS_ITEMS: SettingsNavItem[] = [
|
||||||
{ to: "/parametres/compte", key: "account", Icon: AccountIcon },
|
{ to: "/parametres/compte", key: "account", Icon: AccountIcon },
|
||||||
{ to: "/parametres/preferences", key: "preferences", Icon: DietPreferencesIcon },
|
{ to: "/parametres/preferences", key: "preferences", Icon: DietPreferencesIcon },
|
||||||
{ to: "/parametres/foyer", key: "household", Icon: HouseholdIcon },
|
{ to: "/parametres/foyer", key: "household", Icon: HouseholdIcon },
|
||||||
{ to: "/parametres/preferences-utilisateur", key: "userPreferences", Icon: UserPreferencesIcon },
|
{ to: "/parametres/preferences-utilisateur", key: "userPreferences", Icon: UserPreferencesIcon },
|
||||||
{ to: "/parametres/application", key: "application", Icon: AppSettingsIcon },
|
{ to: "/parametres/application", key: "application", Icon: AppSettingsIcon, nativeOnly: true },
|
||||||
{ to: "/parametres/credits", key: "credits", Icon: CreditsIcon },
|
{ to: "/parametres/credits", key: "credits", Icon: CreditsIcon },
|
||||||
] as const;
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What's actually rendered — {@link SETTINGS_ITEMS} minus the `nativeOnly`
|
||||||
|
* ones on the web. `Capacitor.isNativePlatform()` is fixed for the life of
|
||||||
|
* the page, so this is settled once at module load rather than per render.
|
||||||
|
*/
|
||||||
|
const settingsNavItems: SettingsNavItem[] = SETTINGS_ITEMS.filter(
|
||||||
|
(item) => Capacitor.isNativePlatform() || !item.nativeOnly,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shell for every authenticated page: on desktop/tablet, a sidebar (brand,
|
* Shell for every authenticated page: on desktop/tablet, a sidebar (brand,
|
||||||
|
|
@ -220,7 +247,7 @@ function SettingsMenu() {
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<nav className="app-sidebar__nav app-sidebar__settings-nav">
|
<nav className="app-sidebar__nav app-sidebar__settings-nav">
|
||||||
{SETTINGS_ITEMS.map(({ to, key, Icon }) => (
|
{settingsNavItems.map(({ to, key, Icon }) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={to}
|
key={to}
|
||||||
to={to}
|
to={to}
|
||||||
|
|
@ -379,7 +406,7 @@ function MobileMoreSheet({ onClose }: { onClose: () => void }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="mobile-more-sheet__nav">
|
<nav className="mobile-more-sheet__nav">
|
||||||
{SETTINGS_ITEMS.map(({ to, key, Icon }) => (
|
{settingsNavItems.map(({ to, key, Icon }) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={to}
|
key={to}
|
||||||
to={to}
|
to={to}
|
||||||
|
|
|
||||||
|
|
@ -1378,7 +1378,11 @@
|
||||||
"lede": "Téléchargez le fichier .apk, ou réessayez d'ouvrir l'application si elle est déjà installée sur cet appareil.",
|
"lede": "Téléchargez le fichier .apk, ou réessayez d'ouvrir l'application si elle est déjà installée sur cet appareil.",
|
||||||
"downloadButton": "Télécharger l'APK",
|
"downloadButton": "Télécharger l'APK",
|
||||||
"retryButton": "Réessayer d'ouvrir l'application",
|
"retryButton": "Réessayer d'ouvrir l'application",
|
||||||
"backLink": "Retour à l'accueil"
|
"backLink": "Retour à l'accueil",
|
||||||
|
"stayOnWeb": {
|
||||||
|
"label": "Toujours rester sur le site web",
|
||||||
|
"hint": "Par défaut, ouvrir batchCooking sur votre téléphone propose de basculer vers l'application si elle est installée. Activez cette option pour rester sur le site web à chaque fois."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tour": {
|
"tour": {
|
||||||
"controls": {
|
"controls": {
|
||||||
|
|
@ -1406,13 +1410,7 @@
|
||||||
"checkButton": "Vérifier les mises à jour",
|
"checkButton": "Vérifier les mises à jour",
|
||||||
"checking": "Vérification…",
|
"checking": "Vérification…",
|
||||||
"upToDate": "L'application est à jour.",
|
"upToDate": "L'application est à jour.",
|
||||||
"checkError": "Impossible de vérifier les mises à jour pour le moment.",
|
"checkError": "Impossible de vérifier les mises à jour pour le moment."
|
||||||
"webHint": "La vérification des mises à jour n'est disponible que dans l'application mobile — sur le web, actualiser la page charge toujours la dernière version."
|
|
||||||
},
|
|
||||||
"redirect": {
|
|
||||||
"title": "Ouverture de l'application",
|
|
||||||
"stayOnWebLabel": "Toujours rester sur le site web",
|
|
||||||
"stayOnWebHint": "Par défaut, ouvrir batchCooking sur votre téléphone propose de basculer vers l'application si elle est installée. Activez cette option pour rester sur le site web à chaque fois."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { API_BASE_URL } from "../../api/client";
|
import { API_BASE_URL } from "../../api/client";
|
||||||
import { attemptOpenNativeApp } from "../../lib/mobile-app-redirect";
|
import { CheckboxOption } from "../../components/ui/Checkbox";
|
||||||
|
import { attemptOpenNativeApp, STAY_ON_WEB_PREFERENCE_KEY } from "../../lib/mobile-app-redirect";
|
||||||
import "./download-page.scss";
|
import "./download-page.scss";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -18,9 +20,25 @@ import "./download-page.scss";
|
||||||
* Forgejo's release asset (see that module's own doc comment for why a
|
* Forgejo's release asset (see that module's own doc comment for why a
|
||||||
* direct Forgejo link doesn't work: no CORS headers, no GitHub-style
|
* direct Forgejo link doesn't work: no CORS headers, no GitHub-style
|
||||||
* `/releases/latest/download/<file>` shortcut). Nothing new needed there.
|
* `/releases/latest/download/<file>` shortcut). Nothing new needed there.
|
||||||
|
*
|
||||||
|
* Also home to the "toujours rester sur le site web" toggle (moved here
|
||||||
|
* from "Paramètres d'application" in issue #107 — that page is now packaged-
|
||||||
|
* app only): this is exactly the flow the preference is about, and it's a
|
||||||
|
* public page a logged-out phone-browser visitor can reach. It's written to
|
||||||
|
* `localStorage` on the public web origin, which is where
|
||||||
|
* {@link attemptOpenNativeApp} reads it from (never inside the packaged
|
||||||
|
* app — a different origin, a different `localStorage`).
|
||||||
*/
|
*/
|
||||||
export function DownloadPage() {
|
export function DownloadPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [stayOnWeb, setStayOnWeb] = useState(
|
||||||
|
() => localStorage.getItem(STAY_ON_WEB_PREFERENCE_KEY) === "true",
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleStayOnWebChange(checked: boolean) {
|
||||||
|
setStayOnWeb(checked);
|
||||||
|
localStorage.setItem(STAY_ON_WEB_PREFERENCE_KEY, String(checked));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="download-page">
|
<main className="download-page">
|
||||||
|
|
@ -45,6 +63,13 @@ export function DownloadPage() {
|
||||||
|
|
||||||
<p className="download-page__hint">{t("landing.hero.mobileHint")}</p>
|
<p className="download-page__hint">{t("landing.hero.mobileHint")}</p>
|
||||||
|
|
||||||
|
<div className="download-page__stay-on-web">
|
||||||
|
<CheckboxOption checked={stayOnWeb} onChange={handleStayOnWebChange}>
|
||||||
|
{t("download.stayOnWeb.label")}
|
||||||
|
</CheckboxOption>
|
||||||
|
<p className="download-page__hint">{t("download.stayOnWeb.hint")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Link className="download-page__back" to="/">
|
<Link className="download-page__back" to="/">
|
||||||
{t("download.backLink")}
|
{t("download.backLink")}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,20 @@
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The "toujours rester sur le site web" preference (moved here from
|
||||||
|
// "Paramètres d'application", issue #107) — set off from the download
|
||||||
|
// actions above by a rule + its own spacing, since it's a lasting choice
|
||||||
|
// rather than a one-shot action.
|
||||||
|
.download-page__stay-on-web {
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
padding-top: var(--space-md);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
.download-page__back {
|
.download-page__back {
|
||||||
margin-top: var(--space-sm);
|
margin-top: var(--space-sm);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import { Capacitor } from "@capacitor/core";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { API_BASE_URL } from "../../api/client";
|
import { API_BASE_URL } from "../../api/client";
|
||||||
import { CheckboxOption } from "../../components/ui/Checkbox";
|
|
||||||
import {
|
import {
|
||||||
type AppUpdateCheckResult,
|
type AppUpdateCheckResult,
|
||||||
useAppUpdateCheck,
|
useAppUpdateCheck,
|
||||||
} from "../../features/app-update/useAppUpdateCheck";
|
} from "../../features/app-update/useAppUpdateCheck";
|
||||||
import { useInstalledVersion } from "../../features/app-update/useInstalledVersion";
|
import { useInstalledVersion } from "../../features/app-update/useInstalledVersion";
|
||||||
import { STAY_ON_WEB_PREFERENCE_KEY } from "../../lib/mobile-app-redirect";
|
|
||||||
import "./settings-pages.scss";
|
import "./settings-pages.scss";
|
||||||
|
|
||||||
/** Status of the manual "Vérifier les mises à jour" click — `"idle"` before it's ever been pressed, `"checking"` while in flight, then whatever {@link AppUpdateCheckResult} it resolved to. */
|
/** Status of the manual "Vérifier les mises à jour" click — `"idle"` before it's ever been pressed, `"checking"` while in flight, then whatever {@link AppUpdateCheckResult} it resolved to. */
|
||||||
|
|
@ -21,34 +18,21 @@ type CheckState = "idle" | "checking" | AppUpdateCheckResult["status"];
|
||||||
* where they are, this page is new content only — the installed app's own
|
* where they are, this page is new content only — the installed app's own
|
||||||
* identity/behavior, not how it looks.
|
* identity/behavior, not how it looks.
|
||||||
*
|
*
|
||||||
* Shows a genuinely different section depending on platform, not just a
|
* **Packaged app only.** Its content — the installed version and a "check
|
||||||
* cosmetic tweak — each half of this page is *inert* on the other
|
* for updates" button — has no meaning on the web (there is no "installed
|
||||||
* platform, not merely irrelevant:
|
* version", and `useAppUpdateCheck`'s underlying check is a no-op there),
|
||||||
* - **Version + "Vérifier les mises à jour"** (native only): there is no
|
* so on the web the nav entry is hidden and the route redirects to
|
||||||
* "installed version" concept on the web (see {@link useInstalledVersion}),
|
* `/parametres/compte` (see `AppLayout` / `App.tsx`, issue #107). The
|
||||||
* and `useAppUpdateCheck`'s underlying check itself is a no-op there.
|
* "toujours rester sur le site web" toggle that used to live here — the one
|
||||||
* - **"Toujours rester sur le site web"** (web only, any device — not
|
* thing that was web-only — moved to `/telecharger` (`DownloadPage`), the
|
||||||
* viewport-gated to phone, so it can be set ahead of time): drives
|
* page that whole redirect flow already lands on.
|
||||||
* `attemptOpenNativeApp` (`lib/mobile-app-redirect.ts`), which only ever
|
|
||||||
* runs from a real mobile *browser* tab. Reachable from *inside* the
|
|
||||||
* native app too (it's a normal in-app route), but toggling it there
|
|
||||||
* would be silently pointless: Capacitor's Android WebView runs on its
|
|
||||||
* own fixed `https://localhost` origin, a completely different
|
|
||||||
* `localStorage` than the public website a browser visitor actually
|
|
||||||
* lands on — so writing the preference from inside the app can never
|
|
||||||
* reach the code that reads it. Gating this section to non-native avoids
|
|
||||||
* ever presenting a control that visibly does nothing when toggled.
|
|
||||||
*/
|
*/
|
||||||
export function AppSettingsPage() {
|
export function AppSettingsPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isNative = Capacitor.isNativePlatform();
|
|
||||||
const installedVersion = useInstalledVersion();
|
const installedVersion = useInstalledVersion();
|
||||||
const { checkNow } = useAppUpdateCheck();
|
const { checkNow } = useAppUpdateCheck();
|
||||||
const [checkState, setCheckState] = useState<CheckState>("idle");
|
const [checkState, setCheckState] = useState<CheckState>("idle");
|
||||||
const [foundVersion, setFoundVersion] = useState<string | null>(null);
|
const [foundVersion, setFoundVersion] = useState<string | null>(null);
|
||||||
const [stayOnWeb, setStayOnWeb] = useState(
|
|
||||||
() => localStorage.getItem(STAY_ON_WEB_PREFERENCE_KEY) === "true",
|
|
||||||
);
|
|
||||||
|
|
||||||
async function handleCheckNow() {
|
async function handleCheckNow() {
|
||||||
setCheckState("checking");
|
setCheckState("checking");
|
||||||
|
|
@ -67,11 +51,6 @@ export function AppSettingsPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStayOnWebChange(checked: boolean) {
|
|
||||||
setStayOnWeb(checked);
|
|
||||||
localStorage.setItem(STAY_ON_WEB_PREFERENCE_KEY, String(checked));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-page">
|
<div className="settings-page">
|
||||||
<h1>{t("appSettings.title")}</h1>
|
<h1>{t("appSettings.title")}</h1>
|
||||||
|
|
@ -82,8 +61,6 @@ export function AppSettingsPage() {
|
||||||
{t("appSettings.version.installed", { version: installedVersion })}
|
{t("appSettings.version.installed", { version: installedVersion })}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{isNative ? (
|
|
||||||
<>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void handleCheckNow()}
|
onClick={() => void handleCheckNow()}
|
||||||
|
|
@ -108,21 +85,7 @@ export function AppSettingsPage() {
|
||||||
{checkState === "error" && (
|
{checkState === "error" && (
|
||||||
<p className="field-error">{t("appSettings.version.checkError")}</p>
|
<p className="field-error">{t("appSettings.version.checkError")}</p>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p className="settings-page__hint">{t("appSettings.version.webHint")}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isNative && (
|
|
||||||
<div className="settings-page__section">
|
|
||||||
<h2>{t("appSettings.redirect.title")}</h2>
|
|
||||||
<CheckboxOption checked={stayOnWeb} onChange={handleStayOnWebChange}>
|
|
||||||
{t("appSettings.redirect.stayOnWebLabel")}
|
|
||||||
</CheckboxOption>
|
|
||||||
<p className="settings-page__hint">{t("appSettings.redirect.stayOnWebHint")}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue