Merge lane/offline in feature/craftvia-mvp
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { LogOut } from "lucide-react";
|
||||
import { auth, signOut } from "@/server/auth";
|
||||
import { prisma } from "@/server/db";
|
||||
import { can } from "@/server/services/context";
|
||||
@@ -8,6 +7,8 @@ import { activeTeamIds } from "@/server/services/work-orders/visibility";
|
||||
import { fieldPageContext } from "@/server/services/field/page-context";
|
||||
import { UiLocaleSwitcher } from "@/components/ui-locale-switcher";
|
||||
import { btnSecondary, card } from "@/components/field/ui";
|
||||
import { InstallHint } from "@/components/offline/install-hint";
|
||||
import { LogoutForm } from "@/components/offline/logout-form";
|
||||
|
||||
/** `/m/profile` — name, team, company, language, sign out. */
|
||||
export default async function ProfilePage() {
|
||||
@@ -62,12 +63,9 @@ export default async function ProfilePage() {
|
||||
{t("backoffice")}
|
||||
</Link>
|
||||
)}
|
||||
<form action={logout}>
|
||||
<button type="submit" className={btnSecondary}>
|
||||
<LogOut className="size-5" aria-hidden />
|
||||
{t("logout")}
|
||||
</button>
|
||||
</form>
|
||||
<InstallHint />
|
||||
{/* L7: deletes the local offline data of this tenant/user first (warning if unsent entries exist) */}
|
||||
<LogoutForm action={logout} label={t("logout")} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CraftviaLogo } from "@/components/brand/craftvia-logo";
|
||||
import { AccountInactiveNotice } from "@/components/account-inactive-notice";
|
||||
import { BottomNav } from "@/components/field/bottom-nav";
|
||||
import { OnlineBadge } from "@/components/field/online-badge";
|
||||
import { OfflineRuntime } from "@/components/offline/offline-runtime";
|
||||
|
||||
/**
|
||||
* Mobile shell `/m` (ARCHITEKTUR §5): same session/account/MFA checks as the backoffice
|
||||
@@ -24,6 +25,7 @@ export default async function FieldShell({ children }: Readonly<{ children: Reac
|
||||
</Link>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Slot für L6: <NotificationBell variant="mobile" /> aus src/components/notifications/bell.tsx */}
|
||||
<OfflineRuntime />
|
||||
<OnlineBadge />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { requireModule } from "@/server/modules";
|
||||
|
||||
/** Modul-Gate „field" für die Offline-Ansicht (Lane L7). */
|
||||
export default async function ModuleLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||
await requireModule("field");
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { OfflineView } from "@/components/offline/offline-view";
|
||||
|
||||
/**
|
||||
* `/m/offline` — offline fallback (lane L7): order list/detail from the local bundle. Cached by the
|
||||
* service worker; the content is rendered on the client from IndexedDB, so the cached HTML stays
|
||||
* valid while offline.
|
||||
*/
|
||||
export default async function OfflinePage() {
|
||||
const t = await getTranslations("offline.view");
|
||||
return (
|
||||
<main className="space-y-4 p-4">
|
||||
<h1 className="text-[26px]">{t("title")}</h1>
|
||||
<OfflineView />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { requireModule } from "@/server/modules";
|
||||
|
||||
/** Modul-Gate „field" für die Synchronisationsseite (Lane L7). */
|
||||
export default async function ModuleLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||
await requireModule("field");
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -1,23 +1,17 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { OnlineBadge } from "@/components/field/online-badge";
|
||||
import { card } from "@/components/field/ui";
|
||||
import { SyncPanel } from "@/components/offline/sync-panel";
|
||||
|
||||
/**
|
||||
* PLACEHOLDER (lane L4) — `/m/sync` belongs to lane L7 (Offline/PWA), which replaces this page
|
||||
* with the outbox status, errors and conflicts. Until then it shows the connection state and that
|
||||
* ops are sent immediately (src/lib/field/client-ops.ts).
|
||||
* `/m/sync` (lane L7, Spec §22/§23.4, US-012): connection, last sync, pending changes and uploads
|
||||
* with progress, errors/conflicts in plain language (retry / discard), storage usage, reset. The
|
||||
* data lives on the device (IndexedDB), so the panel is a client component.
|
||||
*/
|
||||
export default async function SyncPlaceholderPage() {
|
||||
const t = await getTranslations("field.sync");
|
||||
export default async function SyncPage() {
|
||||
const t = await getTranslations("offline.sync");
|
||||
return (
|
||||
<main className="space-y-4 p-4">
|
||||
<h1 className="text-[26px]">{t("title")}</h1>
|
||||
<section className={`${card} space-y-3`}>
|
||||
<p className="text-[13px] font-semibold text-muted-foreground">{t("status")}</p>
|
||||
<OnlineBadge large />
|
||||
<p className="text-[15px]">{t("immediate")}</p>
|
||||
<p className="text-[14px] text-muted-foreground">{t("offlineHint")}</p>
|
||||
</section>
|
||||
<SyncPanel />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user