Photo Wall — House of Legends

Documented: 2026-05-11 Doc Status: Excellent | ✓ All 6 checks passed

Overview

Guests can submit photos during their visit. Photos appear on the live “wall” display and other guests can like them. Winning photos are featured.

Photo Submission Flow

Guest at table opens Photo Wall in App, then either takes a photo or selects from gallery, adds an optional caption, and submits. The upload goes to R2 and the photo appears on the Wall Display.

Tables

photoSubmissions: defineTable({
  profileId: v.id("guestProfiles"),
  orderId: v.id("orders"),
  tableId: v.id("tables"),
  imageUrl: v.string(),
  caption: v.optional(v.string()),
  likeCount: v.number(),
  status: v.union(v.literal("ACTIVE"), v.literal("HIDDEN")),
  winner: v.boolean(),
  showDate: v.string(),
  createdAt: v.number(),
})
  .index("by_show_date", ["showDate"])
  .index("by_profile", ["profileId"])
  .index("by_status", ["status"])
  .index("by_likes", ["likeCount", "status"]);

photoLikes: defineTable({
  submissionId: v.id("photoSubmissions"),
  profileId: v.id("guestProfiles"),
  createdAt: v.number(),
})
  .index("by_submission", ["submissionId"])
  .index("by_profile_submission", ["profileId", "submissionId"]);

Challenge Config

challengeConfig: defineTable({
  challengeType: v.union(
    v.literal("PHOTO_WALL"),
    v.literal("LUCKY_SPIN"),
    v.literal("GOOGLE_REVIEW"),
  ),
  enabled: v.boolean(),
  maxValue: v.optional(v.number()),
  prizeDescription: v.optional(v.string()),
  steps: v.array(
    v.object({
      order: v.number(),
      text: v.string(),
      imageUrl: v.optional(v.string()),
    }),
  ),
  activeForDates: v.array(v.string()),
  createdAt: v.number(),
  updatedAt: v.number(),
}).index("by_type", ["challengeType"]);

Components

ComponentPurpose
photo-submission-form.tsxUpload form with camera/gallery
photo-card.tsxIndividual photo with likes
photo-wall-grid.tsxMasonry grid of photos
photo-likes.tsxLike button + count
wall-display.tsxFull-screen wall for venue display

Like Flow

  1. Guest taps heart on photo
  2. photoLikes record created
  3. photoSubmissions.likeCount incremented
  4. Heart fills in UI

Winner Selection

Admin selects winning photos:
  • Set winner: true on selected photos
  • Winners featured at top of wall
  • Winners eligible for prize (comp items, discounts)

Backend Functions

FunctionPurpose
minigames.submitPhotoUpload + create submission
minigames.likePhotoAdd like
minigames.getWallPhotosGet photos for date
minigames.selectWinnerMark photo as winner