diff --git a/src/components/auth/login-form.tsx b/src/components/auth/login-form.tsx
index ffa2b6f..a94af76 100644
--- a/src/components/auth/login-form.tsx
+++ b/src/components/auth/login-form.tsx
@@ -2,6 +2,7 @@ import { useState, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
+import { enableDemoMode } from "@/lib/demo";
interface LoginFormProps {
onSendOtp: (phone: string) => Promise<{ request_id: string }>;
@@ -132,6 +133,19 @@ export function LoginForm({ onSendOtp, onVerifyOtp }: LoginFormProps) {
)}
+
+
+
+
);
}
diff --git a/src/components/layout/app-shell.tsx b/src/components/layout/app-shell.tsx
index 3f6c5d8..5f5dc8c 100644
--- a/src/components/layout/app-shell.tsx
+++ b/src/components/layout/app-shell.tsx
@@ -2,6 +2,7 @@ import { useNavigate } from "react-router";
import { Plus } from "lucide-react";
import { Header } from "./header";
import { ROUTES } from "@/lib/constants";
+import { disableDemoMode, isDemoMode } from "@/lib/demo";
interface AppShellProps {
children: React.ReactNode;
@@ -10,9 +11,24 @@ interface AppShellProps {
export function AppShell({ children, showFab = false }: AppShellProps) {
const navigate = useNavigate();
+ const demo = isDemoMode();
return (
+ {demo && (
+
+ Demo-Modus — Aenderungen werden nicht gespeichert
+
+
+ )}
{children}
{showFab && (
diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts
index c15cdf0..bcf6104 100644
--- a/src/hooks/use-auth.ts
+++ b/src/hooks/use-auth.ts
@@ -1,6 +1,13 @@
import { useCallback, useEffect, useState } from "react";
import type { Session, User } from "@supabase/supabase-js";
import { supabase } from "@/lib/supabase";
+import {
+ DEMO_PROFILE,
+ DEMO_SESSION,
+ DEMO_USER,
+ disableDemoMode,
+ isDemoMode,
+} from "@/lib/demo";
import type { Profile } from "@/types";
interface AuthState {
@@ -12,13 +19,25 @@ interface AuthState {
}
export function useAuth() {
- const [state, setState] = useState
({
- session: null,
- user: null,
- profile: null,
- loading: true,
- isNewUser: false,
- });
+ const demo = isDemoMode();
+
+ const [state, setState] = useState(() =>
+ demo
+ ? {
+ session: DEMO_SESSION,
+ user: DEMO_USER,
+ profile: DEMO_PROFILE,
+ loading: false,
+ isNewUser: false,
+ }
+ : {
+ session: null,
+ user: null,
+ profile: null,
+ loading: true,
+ isNewUser: false,
+ },
+ );
const fetchProfile = useCallback(async (userId: string) => {
const { data } = await supabase
@@ -30,6 +49,7 @@ export function useAuth() {
}, []);
useEffect(() => {
+ if (demo) return;
supabase.auth.getSession().then(async ({ data: { session } }) => {
if (session?.user) {
const profile = await fetchProfile(session.user.id);
@@ -69,7 +89,7 @@ export function useAuth() {
});
return () => subscription.unsubscribe();
- }, [fetchProfile]);
+ }, [fetchProfile, demo]);
const sendOtp = useCallback(async (phone: string) => {
const { data, error } = await supabase.functions.invoke("send-otp", {
@@ -117,8 +137,13 @@ export function useAuth() {
);
const signOut = useCallback(async () => {
+ if (demo) {
+ disableDemoMode();
+ window.location.reload();
+ return;
+ }
await supabase.auth.signOut();
- }, []);
+ }, [demo]);
return {
...state,
diff --git a/src/hooks/use-listings.ts b/src/hooks/use-listings.ts
index 22e3e98..f193c6f 100644
--- a/src/hooks/use-listings.ts
+++ b/src/hooks/use-listings.ts
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { supabase } from "@/lib/supabase";
+import { DEMO_LISTINGS, isDemoMode } from "@/lib/demo";
import type { Listing, ListingInsert, ListingUpdate, ListingType } from "@/types";
interface UseListingsOptions {
@@ -8,13 +9,26 @@ interface UseListingsOptions {
export function useListings(options: UseListingsOptions = {}) {
const { typeFilter = "alle" } = options;
- const [listings, setListings] = useState([]);
- const [loading, setLoading] = useState(true);
+ const demo = isDemoMode();
+
+ const filterDemo = (all: Listing[]) =>
+ typeFilter === "alle" ? all : all.filter((l) => l.type === typeFilter);
+
+ const [listings, setListings] = useState(() =>
+ demo ? filterDemo(DEMO_LISTINGS) : [],
+ );
+ const [loading, setLoading] = useState(!demo);
const [error, setError] = useState(null);
const [newCount, setNewCount] = useState(0);
const pendingRef = useRef([]);
const fetchListings = useCallback(async () => {
+ if (demo) {
+ setListings(filterDemo(DEMO_LISTINGS));
+ setLoading(false);
+ return;
+ }
+
setLoading(true);
setError(null);
@@ -38,13 +52,15 @@ export function useListings(options: UseListingsOptions = {}) {
setLoading(false);
setNewCount(0);
pendingRef.current = [];
- }, [typeFilter]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [typeFilter, demo]);
useEffect(() => {
fetchListings();
}, [fetchListings]);
useEffect(() => {
+ if (demo) return;
const channel = supabase
.channel("listings-realtime")
.on(
@@ -83,7 +99,7 @@ export function useListings(options: UseListingsOptions = {}) {
return () => {
supabase.removeChannel(channel);
};
- }, []);
+ }, [demo]);
const showPending = useCallback(() => {
setListings((prev) => {
@@ -101,6 +117,37 @@ export function useListings(options: UseListingsOptions = {}) {
const createListing = useCallback(
async (listing: Omit, userId: string) => {
+ if (demo) {
+ const now = new Date().toISOString();
+ const mock: Listing = {
+ id: crypto.randomUUID(),
+ user_id: userId,
+ type: listing.type,
+ origin_label: listing.origin_label,
+ origin_city: listing.origin_city,
+ origin_lat: listing.origin_lat,
+ origin_lng: listing.origin_lng,
+ destination_label: listing.destination_label,
+ destination_city: listing.destination_city,
+ destination_lat: listing.destination_lat,
+ destination_lng: listing.destination_lng,
+ departure_at: listing.departure_at,
+ seats: listing.seats,
+ notes: listing.notes ?? null,
+ created_at: now,
+ updated_at: now,
+ };
+ DEMO_LISTINGS.push(mock);
+ setListings((prev) =>
+ [...prev, mock].sort(
+ (a, b) =>
+ new Date(a.departure_at).getTime() -
+ new Date(b.departure_at).getTime(),
+ ),
+ );
+ return mock;
+ }
+
const { data, error } = await supabase
.from("listings")
.insert({ ...listing, user_id: userId })
@@ -117,11 +164,26 @@ export function useListings(options: UseListingsOptions = {}) {
);
return data;
},
- [],
+ [demo],
);
const updateListing = useCallback(
async (id: string, updates: ListingUpdate) => {
+ if (demo) {
+ const idx = DEMO_LISTINGS.findIndex((l) => l.id === id);
+ if (idx === -1) throw new Error("Eintrag nicht gefunden");
+ const updated: Listing = {
+ ...DEMO_LISTINGS[idx]!,
+ ...updates,
+ updated_at: new Date().toISOString(),
+ };
+ DEMO_LISTINGS[idx] = updated;
+ setListings((prev) =>
+ prev.map((l) => (l.id === id ? updated : l)),
+ );
+ return updated;
+ }
+
const { data, error } = await supabase
.from("listings")
.update(updates)
@@ -132,25 +194,42 @@ export function useListings(options: UseListingsOptions = {}) {
if (error) throw error;
return data;
},
- [],
+ [demo],
);
- const deleteListing = useCallback(async (id: string) => {
- const { error } = await supabase.from("listings").delete().eq("id", id);
- if (error) throw error;
- setListings((prev) => prev.filter((l) => l.id !== id));
- }, []);
+ const deleteListing = useCallback(
+ async (id: string) => {
+ if (demo) {
+ const idx = DEMO_LISTINGS.findIndex((l) => l.id === id);
+ if (idx !== -1) DEMO_LISTINGS.splice(idx, 1);
+ setListings((prev) => prev.filter((l) => l.id !== id));
+ return;
+ }
+ const { error } = await supabase.from("listings").delete().eq("id", id);
+ if (error) throw error;
+ setListings((prev) => prev.filter((l) => l.id !== id));
+ },
+ [demo],
+ );
- const getListing = useCallback(async (id: string) => {
- const { data, error } = await supabase
- .from("listings")
- .select("*")
- .eq("id", id)
- .single();
+ const getListing = useCallback(
+ async (id: string) => {
+ if (demo) {
+ const found = DEMO_LISTINGS.find((l) => l.id === id);
+ if (!found) throw new Error("Eintrag nicht gefunden");
+ return found;
+ }
+ const { data, error } = await supabase
+ .from("listings")
+ .select("*")
+ .eq("id", id)
+ .single();
- if (error) throw error;
- return data;
- }, []);
+ if (error) throw error;
+ return data;
+ },
+ [demo],
+ );
const groupedByCity = listings.reduce>(
(acc, listing) => {
diff --git a/src/hooks/use-matches.ts b/src/hooks/use-matches.ts
index 4e6e5fc..6e54907 100644
--- a/src/hooks/use-matches.ts
+++ b/src/hooks/use-matches.ts
@@ -1,5 +1,6 @@
import { useCallback, useState } from "react";
import { supabase } from "@/lib/supabase";
+import { DEMO_LISTINGS, isDemoMode } from "@/lib/demo";
export interface MatchResult {
listing_id: string;
@@ -12,6 +13,22 @@ export interface MatchResult {
distance_destination_km: number;
}
+function haversineKm(
+ lat1: number,
+ lng1: number,
+ lat2: number,
+ lng2: number,
+): number {
+ const r = 6371;
+ const toRad = (d: number) => (d * Math.PI) / 180;
+ const dlat = toRad(lat2 - lat1);
+ const dlng = toRad(lng2 - lng1);
+ const a =
+ Math.sin(dlat / 2) ** 2 +
+ Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dlng / 2) ** 2;
+ return r * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+}
+
export function useMatches() {
const [matches, setMatches] = useState([]);
const [loading, setLoading] = useState(false);
@@ -19,6 +36,61 @@ export function useMatches() {
const findMatches = useCallback(async (listingId: string) => {
setLoading(true);
try {
+ if (isDemoMode()) {
+ const src = DEMO_LISTINGS.find((l) => l.id === listingId);
+ if (!src) {
+ setMatches([]);
+ return [];
+ }
+ const counterType = src.type === "angebot" ? "anfrage" : "angebot";
+ const results: MatchResult[] = DEMO_LISTINGS.filter((l) => {
+ if (l.type !== counterType) return false;
+ if (l.user_id === src.user_id) return false;
+ const dOrigin = haversineKm(
+ src.origin_lat,
+ src.origin_lng,
+ l.origin_lat,
+ l.origin_lng,
+ );
+ const dDest = haversineKm(
+ src.destination_lat,
+ src.destination_lng,
+ l.destination_lat,
+ l.destination_lng,
+ );
+ if (dOrigin > 15 || dDest > 15) return false;
+ const timeDiff = Math.abs(
+ new Date(l.departure_at).getTime() -
+ new Date(src.departure_at).getTime(),
+ );
+ if (timeDiff > 2 * 3600_000) return false;
+ if (src.type === "anfrage" && l.seats < src.seats) return false;
+ if (src.type === "angebot" && l.seats > src.seats) return false;
+ return true;
+ }).map((l) => ({
+ listing_id: l.id,
+ user_id: l.user_id,
+ origin_label: l.origin_label,
+ destination_label: l.destination_label,
+ departure_at: l.departure_at,
+ seats: l.seats,
+ distance_origin_km: haversineKm(
+ src.origin_lat,
+ src.origin_lng,
+ l.origin_lat,
+ l.origin_lng,
+ ),
+ distance_destination_km: haversineKm(
+ src.destination_lat,
+ src.destination_lng,
+ l.destination_lat,
+ l.destination_lng,
+ ),
+ }));
+ setMatches(results);
+ return results;
+ }
+
const { data, error } = await supabase.rpc("find_matches", {
p_listing_id: listingId,
});
diff --git a/src/hooks/use-profile.ts b/src/hooks/use-profile.ts
index 987046b..b9e8b40 100644
--- a/src/hooks/use-profile.ts
+++ b/src/hooks/use-profile.ts
@@ -1,6 +1,7 @@
import { useCallback } from "react";
import { supabase } from "@/lib/supabase";
import { useAuthContext } from "@/hooks/use-auth-context";
+import { isDemoMode } from "@/lib/demo";
export function useProfile() {
const { profile, user } = useAuthContext();
@@ -13,6 +14,11 @@ export function useProfile() {
}) => {
if (!user) throw new Error("Nicht eingeloggt.");
+ if (isDemoMode()) {
+ // Aenderungen verfallen mit Reload — Demo-Profile bleibt unveraendert
+ return;
+ }
+
const { error } = await supabase
.from("profiles")
.update(updates)
diff --git a/src/hooks/use-profiles.ts b/src/hooks/use-profiles.ts
index 4898cce..06d369c 100644
--- a/src/hooks/use-profiles.ts
+++ b/src/hooks/use-profiles.ts
@@ -1,11 +1,15 @@
import { useCallback, useEffect, useState } from "react";
import { supabase } from "@/lib/supabase";
+import { DEMO_PROFILES, isDemoMode } from "@/lib/demo";
import type { Profile } from "@/types";
export function useProfiles(userIds: string[]) {
- const [profiles, setProfiles] = useState>({});
+ const [profiles, setProfiles] = useState>(() =>
+ isDemoMode() ? { ...DEMO_PROFILES } : {},
+ );
const fetchProfiles = useCallback(async (ids: string[]) => {
+ if (isDemoMode()) return;
const missing = ids.filter((id) => !profiles[id]);
if (missing.length === 0) return;
diff --git a/src/hooks/use-push.ts b/src/hooks/use-push.ts
index 5eaa8cb..e215c69 100644
--- a/src/hooks/use-push.ts
+++ b/src/hooks/use-push.ts
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { supabase } from "@/lib/supabase";
+import { isDemoMode } from "@/lib/demo";
interface UsePushOptions {
userId: string | undefined;
@@ -11,6 +12,10 @@ export function usePush({ userId }: UsePushOptions) {
const [permission, setPermission] = useState("default");
useEffect(() => {
+ if (isDemoMode()) {
+ setIsSupported(false);
+ return;
+ }
const supported =
"serviceWorker" in navigator &&
"PushManager" in window &&
diff --git a/src/lib/demo.ts b/src/lib/demo.ts
new file mode 100644
index 0000000..e0ad129
--- /dev/null
+++ b/src/lib/demo.ts
@@ -0,0 +1,246 @@
+import type { Listing, Profile } from "@/types";
+import type { User, Session } from "@supabase/supabase-js";
+
+const DEMO_KEY = "kommit_demo";
+
+export function isDemoMode(): boolean {
+ if (typeof window === "undefined") return false;
+ return localStorage.getItem(DEMO_KEY) === "1";
+}
+
+export function enableDemoMode() {
+ localStorage.setItem(DEMO_KEY, "1");
+}
+
+export function disableDemoMode() {
+ localStorage.removeItem(DEMO_KEY);
+}
+
+const DEMO_USER_ID = "demo-user-00000000-0000-0000-0000-000000000001";
+const NOW = new Date();
+
+function hoursFromNow(h: number): string {
+ return new Date(NOW.getTime() + h * 3600_000).toISOString();
+}
+
+function daysAgo(d: number): string {
+ return new Date(NOW.getTime() - d * 86400_000).toISOString();
+}
+
+export const DEMO_USER: User = {
+ id: DEMO_USER_ID,
+ aud: "authenticated",
+ role: "authenticated",
+ email: "",
+ phone: "+491701234567",
+ app_metadata: {},
+ user_metadata: {},
+ created_at: daysAgo(120),
+ updated_at: daysAgo(1),
+};
+
+export const DEMO_SESSION: Session = {
+ access_token: "demo",
+ refresh_token: "demo",
+ expires_in: 3600,
+ expires_at: Math.floor(NOW.getTime() / 1000) + 3600,
+ token_type: "bearer",
+ user: DEMO_USER,
+};
+
+export const DEMO_PROFILE: Profile = {
+ id: DEMO_USER_ID,
+ first_name: "Max",
+ last_name: "Mustermann",
+ phone: "+491701234567",
+ show_phone: true,
+ telegram_chat_id: null,
+ role: "admin",
+ avatar_color: "#FF8A4C",
+ created_at: daysAgo(120),
+ updated_at: daysAgo(1),
+};
+
+const OTHER_PROFILES: Profile[] = [
+ {
+ id: "demo-user-00000000-0000-0000-0000-000000000002",
+ first_name: "Anna",
+ last_name: "Schmidt",
+ phone: "+491729876543",
+ show_phone: true,
+ telegram_chat_id: null,
+ role: "user",
+ avatar_color: "#3DA9FC",
+ created_at: daysAgo(80),
+ updated_at: daysAgo(2),
+ },
+ {
+ id: "demo-user-00000000-0000-0000-0000-000000000003",
+ first_name: "Lukas",
+ last_name: "Mueller",
+ phone: "+491601112233",
+ show_phone: false,
+ telegram_chat_id: null,
+ role: "user",
+ avatar_color: "#10B981",
+ created_at: daysAgo(45),
+ updated_at: daysAgo(5),
+ },
+ {
+ id: "demo-user-00000000-0000-0000-0000-000000000004",
+ first_name: "Sophie",
+ last_name: "Weber",
+ phone: "+491775556677",
+ show_phone: true,
+ telegram_chat_id: null,
+ role: "user",
+ avatar_color: "#F97066",
+ created_at: daysAgo(15),
+ updated_at: daysAgo(3),
+ },
+];
+
+export const DEMO_PROFILES: Record = {
+ [DEMO_PROFILE.id]: DEMO_PROFILE,
+ ...Object.fromEntries(OTHER_PROFILES.map((p) => [p.id, p])),
+};
+
+// Lat/lng grob fuer drei Orte im Breisgau
+const BOETZINGEN = { city: "Boetzingen", lat: 48.0689, lng: 7.7172 };
+const FREIBURG = { city: "Freiburg im Breisgau", lat: 47.9959, lng: 7.8494 };
+const ENDINGEN = { city: "Endingen am Kaiserstuhl", lat: 48.1417, lng: 7.7042 };
+const LILIENHOF = {
+ city: "Freiburg im Breisgau",
+ label: "Lilienhof, Freiburg",
+ lat: 47.9892,
+ lng: 7.8627,
+};
+
+export const DEMO_LISTINGS: Listing[] = [
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000001",
+ user_id: OTHER_PROFILES[0]!.id,
+ type: "angebot",
+ origin_label: `Bahnhof ${BOETZINGEN.city}`,
+ origin_city: BOETZINGEN.city,
+ origin_lat: BOETZINGEN.lat,
+ origin_lng: BOETZINGEN.lng,
+ destination_label: LILIENHOF.label,
+ destination_city: LILIENHOF.city,
+ destination_lat: LILIENHOF.lat,
+ destination_lng: LILIENHOF.lng,
+ departure_at: hoursFromNow(5),
+ seats: 3,
+ notes: "Treffpunkt am Bahnhofsvorplatz. Bin puenktlich!",
+ created_at: hoursFromNow(-2),
+ updated_at: hoursFromNow(-2),
+ },
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000002",
+ user_id: OTHER_PROFILES[1]!.id,
+ type: "anfrage",
+ origin_label: `Marktplatz ${BOETZINGEN.city}`,
+ origin_city: BOETZINGEN.city,
+ origin_lat: BOETZINGEN.lat + 0.002,
+ origin_lng: BOETZINGEN.lng + 0.001,
+ destination_label: LILIENHOF.label,
+ destination_city: LILIENHOF.city,
+ destination_lat: LILIENHOF.lat,
+ destination_lng: LILIENHOF.lng,
+ departure_at: hoursFromNow(5.5),
+ seats: 1,
+ notes: null,
+ created_at: hoursFromNow(-1),
+ updated_at: hoursFromNow(-1),
+ },
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000003",
+ user_id: DEMO_USER_ID,
+ type: "angebot",
+ origin_label: `Freiburg Hauptbahnhof`,
+ origin_city: FREIBURG.city,
+ origin_lat: FREIBURG.lat,
+ origin_lng: FREIBURG.lng,
+ destination_label: `Endingen Rathaus`,
+ destination_city: ENDINGEN.city,
+ destination_lat: ENDINGEN.lat,
+ destination_lng: ENDINGEN.lng,
+ departure_at: hoursFromNow(28),
+ seats: 2,
+ notes: "Habe Kofferraum frei fuer Instrumente.",
+ created_at: hoursFromNow(-5),
+ updated_at: hoursFromNow(-5),
+ },
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000004",
+ user_id: OTHER_PROFILES[2]!.id,
+ type: "anfrage",
+ origin_label: `Endingen Bahnhof`,
+ origin_city: ENDINGEN.city,
+ origin_lat: ENDINGEN.lat,
+ origin_lng: ENDINGEN.lng,
+ destination_label: LILIENHOF.label,
+ destination_city: LILIENHOF.city,
+ destination_lat: LILIENHOF.lat,
+ destination_lng: LILIENHOF.lng,
+ departure_at: hoursFromNow(6),
+ seats: 1,
+ notes: "Kann bei Bedarf etwas Spritgeld zahlen.",
+ created_at: hoursFromNow(-3),
+ updated_at: hoursFromNow(-3),
+ },
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000005",
+ user_id: OTHER_PROFILES[0]!.id,
+ type: "angebot",
+ origin_label: `Endingen Marktplatz`,
+ origin_city: ENDINGEN.city,
+ origin_lat: ENDINGEN.lat + 0.001,
+ origin_lng: ENDINGEN.lng + 0.001,
+ destination_label: LILIENHOF.label,
+ destination_city: LILIENHOF.city,
+ destination_lat: LILIENHOF.lat,
+ destination_lng: LILIENHOF.lng,
+ departure_at: hoursFromNow(29),
+ seats: 4,
+ notes: "Faehrt morgens auch Richtung Freiburg!",
+ created_at: hoursFromNow(-4),
+ updated_at: hoursFromNow(-4),
+ },
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000006",
+ user_id: OTHER_PROFILES[1]!.id,
+ type: "anfrage",
+ origin_label: `Freiburg Stuehlinger`,
+ origin_city: FREIBURG.city,
+ origin_lat: FREIBURG.lat - 0.005,
+ origin_lng: FREIBURG.lng - 0.01,
+ destination_label: `Endingen Rathaus`,
+ destination_city: ENDINGEN.city,
+ destination_lat: ENDINGEN.lat,
+ destination_lng: ENDINGEN.lng,
+ departure_at: hoursFromNow(29.5),
+ seats: 2,
+ notes: null,
+ created_at: hoursFromNow(-6),
+ updated_at: hoursFromNow(-6),
+ },
+ {
+ id: "demo-listing-00000000-0000-0000-0000-000000000007",
+ user_id: OTHER_PROFILES[3]!.id,
+ type: "angebot",
+ origin_label: `Boetzingen Kirche`,
+ origin_city: BOETZINGEN.city,
+ origin_lat: BOETZINGEN.lat - 0.001,
+ origin_lng: BOETZINGEN.lng - 0.001,
+ destination_label: LILIENHOF.label,
+ destination_city: LILIENHOF.city,
+ destination_lat: LILIENHOF.lat,
+ destination_lng: LILIENHOF.lng,
+ departure_at: hoursFromNow(50),
+ seats: 3,
+ notes: "Naechste Woche Mittwoch.",
+ created_at: hoursFromNow(-8),
+ updated_at: hoursFromNow(-8),
+ },
+];