Hearts on activities, feedback triage by admins, click-to-permalink
Three small features and one UX bug.
1. **Hearts.** New `activity_hearts(activity_id, user_id, created_at)`
table with composite PK. POST/DELETE /api/activities/:id/heart for
logged-in users on any non-private activity. Idempotent — re-posting
is a no-op rather than a 409.
Activity serialisation now includes `heart_count` and
`viewer_hearted` on all three visibility variants (private is always
0/false; hearts make no sense there). ActivityRow renders a toggle
button with optimistic updates and a local override that snaps back
on network error, then propagates the server's authoritative state
to the parent list via a new `onChanged` callback.
2. **Admin can triage feedback.** Added `done_at` + `done_by` columns
to feedback (migrated via ensureColumn for older scaffold DBs). New
admin-only endpoints:
PATCH /api/feedback/:id — { done: boolean }; sets/clears done_at
DELETE /api/feedback/:id — drops the entry
The Feedback list view sorts open requests above done ones, and
admins see "Marker som ferdig" / "Marker som åpen" / "Slett" buttons
per entry. Open/done badges visible to everyone with read access
(i.e., moderators).
3. **Clicking an activity opens its permalink.** Activity titles in
ActivityRow are now anchor links to /a/:id, so clicking the title
navigates to the permalink view. Action buttons (heart, edit,
delete, del/copy-link) stay inside the card; the anchor only wraps
the title text, so taps elsewhere don't navigate.
Bundle gained 1 KB gzipped (30.2 → 31.2 KB). 26 tests still pass,
typecheck clean.
This commit is contained in:
parent
bd82f71a01
commit
5c9455c3f3
9 changed files with 305 additions and 31 deletions
|
|
@ -123,6 +123,10 @@ export interface FeedbackEntry {
|
|||
kind: FeedbackKind;
|
||||
body: string;
|
||||
created_at: number;
|
||||
/** Set when an admin has marked the entry as done. */
|
||||
done_at: number | null;
|
||||
/** User id of the admin who marked it done; null when done_at is null. */
|
||||
done_by: string | null;
|
||||
// Moderator-only fields; included when the caller is a moderator viewing
|
||||
// the list. (The submit endpoint doesn't return these — a submitter doesn't
|
||||
// need to see them.)
|
||||
|
|
@ -131,6 +135,10 @@ export interface FeedbackEntry {
|
|||
user_display?: string | null;
|
||||
}
|
||||
|
||||
export interface FeedbackUpdateRequest {
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
// --- Activities --------------------------------------------------------------
|
||||
export interface ActivityPublic {
|
||||
id: string;
|
||||
|
|
@ -146,6 +154,10 @@ export interface ActivityPublic {
|
|||
loc_lat: number | null;
|
||||
loc_lng: number | null;
|
||||
scheduled_at: number | null;
|
||||
/** Total hearts on this activity. */
|
||||
heart_count: number;
|
||||
/** True when the authenticated viewer has hearted this activity. */
|
||||
viewer_hearted: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
|
@ -163,6 +175,8 @@ export interface ActivitySemi {
|
|||
loc_lat: number | null;
|
||||
loc_lng: number | null;
|
||||
scheduled_at: number | null;
|
||||
heart_count: number;
|
||||
viewer_hearted: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
|
@ -173,6 +187,11 @@ export interface ActivityPrivate {
|
|||
owner_id: string; // always you — server only returns your private rows
|
||||
ciphertext: string; // base64
|
||||
nonce: string; // base64
|
||||
// Always 0 / false for private rows — hearts don't apply (nobody else sees
|
||||
// them). Kept in the type so the client doesn't need a discriminator check
|
||||
// before reading the field.
|
||||
heart_count: number;
|
||||
viewer_hearted: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue