Add gamification badges - #3
Conversation
|
Rebased onto fresh main, squashed minor fixes, force-pushed. |
|
Added two more dashboard style interfaces on the kiosk screen - location leaderboard and member badges - both are only loaded on demand so shouldnt hurt performance unless clicked. UI on that kiosk is hard to work with, really need to do a CSS redo on it |
|
ive just ran a bunch of tests of the digest emails and had to make some web resource changes to support making that email html not look like total crap. |
|
@sdunster logic I went with is if a members home location has gamification enabled members will earn badges but if the location they are signing in at doesn't have them enabled they won't be displayed, next sign in at an enabled location they will get the popup and have them awarded. This logic extends to badges awarded from API calls and overnight code runs (they get to see them next sign in) |
3f7ad4a to
b9aed52
Compare
|
@sdunster a question for you - the email it sends links back to the svg's in the web - how do you want to handle url's for these with prod/dev etc |
b9aed52 to
9105299
Compare
sdunster
left a comment
There was a problem hiding this comment.
extremely light review - many parts not yet covered - please dont act on it yet
random thoughts:
- these icons may go further if you extracted just the icon from the background and overlayed them on either an SVG or just a html/css circle background. This way you could have bronze, silver, gold, platinum variants all using the same SVG "icon"/"foreground"
- for rollout I reckon we should potentially even go further and make the logic such that we start by allowlisting gamification to individual people
- perhaps it'd make sense to have 2 different axes for badge grouping.. one is the "tier"/"class" ie bronze, silver, gold and the other is the "group"/"type"/"category" ie your passport ones vs streaks vs .. more of like a functional thing rather than how significant the achievement is?
| Ok(Some(id)) => id, | ||
| Ok(None) => return true, | ||
| Ok(None) => { | ||
| info!("request_auth_code: no user found for email={}", email); |
| end_time: i64, | ||
| category_id: ID, | ||
| ) -> Result<Period<A>> { | ||
| ) -> Result<ScanSignOutResult<A>> { |
There was a problem hiding this comment.
totally get why this changed but can't just do it this way. Gotta make a new "scan_sign_out2" variant so we don't break existing callers during rollout. Most of the time the window is small but kiosks sometimes take a while to get the new code. Best way to validate this (CI will flag it for you) is to split backend from frontend changes into separate PRs so you simulate them releasing at different times.
| use crate::db; | ||
| use crate::mail; | ||
|
|
||
| const DEFAULT_BADGE_ICON_BASE_URL: &str = "https://new.seslogin.com/image/badges"; |
There was a problem hiding this comment.
this is fine for now. The links will be broken until the images make it to prod. If you'd like for that not to be the case you could split them out to a separate PR and land that first.
| email: Option<&'a str>, | ||
| }, | ||
| BadgeState { | ||
| badge_state: serde_json::Map<String, serde_json::Value>, |
There was a problem hiding this comment.
sdunster: think about this some more - should we prefer richer types or is this just passthrough?
| (None, Some(msg)) => msg.to_string(), | ||
| (None, None) => format!("{e}"), | ||
| // For non-service failures (dispatch/timeout), Debug includes connector cause. | ||
| (None, None) => format!("{e:?}"), |
| # CI/CD authenticates via GitHub OIDC — the OIDC provider, deploy role, and its | ||
| # scoped policies (Lambda code update, S3 web sync, CloudFront invalidation) are | ||
| # defined in iam_oidc.tf (added in Phase 5). | ||
| ## Human/admin access is via IAM Identity Center (SSO) — see the SesloginAdmin |
| }); | ||
| } | ||
|
|
||
| function formatRelativeHours(tsSeconds: number): string { |
There was a problem hiding this comment.
move to time.ts or use an existing function from there
| return `inline-flex min-w-5.5 items-center justify-center rounded-full border px-2 py-0.75 text-[0.85rem] font-extrabold ${TIER_PILL_CLASS[tierCssClass(tier)]}`; | ||
| } | ||
|
|
||
| function LeaderboardModalLoadingFallback(props: { |
There was a problem hiding this comment.
these functional components all really should be separate files
| <ScanTitleBar | ||
| onCancelSignOut={cancelSignOut ?? undefined} | ||
| signingOutName={signingOutName ?? undefined} | ||
| signingOutPersonId={signingOutPersonId ?? undefined} |
| infra/terraform.tfvars | ||
| infra/.terraform/ | ||
| infra/.terraform.lock.hcl | ||
| .vscode/settings.json |
a70a558 to
6a086ba
Compare
PersonBadge/PersonBadgeProgress ids were just the badge-type id (e.g.
first-signin-location-<locationId>), which is shared by every member
who first signed in at that location. Since Relay normalizes its store
by raw id, responses for different members collided and silently
clobbered each other's awardedAt (and, once badgeId was split out,
PersonBadge/PersonBadgeProgress collided with each other too). Scope
ids per-person and per-type ("PersonBadge:<personId>:<badgeId>"), and
expose the badge-type id separately as badgeId for icon/type lookups.
Also includes the in-progress badge catalog/icon work: a new April
Fools' Day badge, badge digest/export fixes, and new SVG badge assets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Members should choose which of their own locations get a weekly badge
digest email, matching the existing daily email summary pattern,
instead of an admin-wide location toggle sending to everyone with
access.
Exposes current/target counts for badges backed by a running counter
(sign-ins, category thresholds, streaks), computed from the per-location
counters already stored on the Person record, so no extra DB reads are
needed. Manual (nightly-batch/easter-egg) and fixed-date badges keep the
plain earned/locked state since they have no meaningful running total.
Renders as a compact progress bar in the shared badge panel used by the
admin member page, the badges page, and the kiosk modal.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Previously the "new badge" prompt was shown whenever the member's home
location had gamification enabled, regardless of whether the location
they were currently scanning at had it disabled. Now the celebration
is only shown when the current location has gamification enabled;
pending awards from any location are left undisplayed (badges are
global to the person, not scoped to a location) so they surface next
time the member is somewhere gamification is on, deduped so a badge
independently re-triggered at a second location doesn't double-pop.
Also fixes a passport (first-signin-at-location) badge showing the
wrong location name/description once it could surface at a location
other than where it was earned.
Give the location "first sign-in" badge icon actual stamp iconography
(worn double ring, star emblem, ribboned unit text, tilted impression)
instead of plain concentric circles with a stray unused ring.
Replace the two crossed lines and dot placeholder with a recognizable
wrench glyph.
Replace the plain filled circle placeholder with a folded map, dotted
trail, and X marking the spot.
Extract the tier key/CSS-class/label helpers out of
MemberBadgeProgressPanel into a shared lib/badgeTiers module, and use
them in the members list to replace the single badge-count-plus-last-
earned cell with a breakdown of counts per tier (gold/silver/bronze/
starter/passport stamps).
Add a tierCounts field to the badgeLeaderboard GraphQL entry, grouping
each person's awarded badges by tier. Use it in the kiosk leaderboard
to show a per-tier pill breakdown on each row and an aggregate
breakdown in the summary bar, instead of a single opaque total.
createPeriod bypassed badge progress entirely since only scan_register2/
scan_sign_out called apply_badge_event. Now createPeriod feeds a synthetic
check-in/sign-out pair through the same counters/streaks/easter-egg logic,
using the period's own start/end time rather than wall-clock time (so
backdated entries evaluate holiday/streak rules against when the period
actually happened). Any award earned this way is left undisplayed so it
surfaces on the member's next real kiosk visit rather than being silently
consumed with no UI to show it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The gamification branch predated the full CSS->Tailwind conversion on
main and kept adding to style.css/kiosk.css/global.css. Those files
were resurrected during the rebase so history would apply cleanly;
this converts their remaining rules (badge cards, progress bars, kiosk
leaderboard/member-badges modals, celebration overlay, tier pills) to
Tailwind utility classes and deletes the legacy stylesheets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add bottom margin below the tier-count pill row on the kiosk leaderboard
so it doesn't butt up against the member list.
- Explicitly center tier/source group headers in MemberBadgeProgressPanel
instead of relying on inherited page text-align, so kiosk and admin render
it the same way.
- Restore pointer-events: none on the badge celebration confetti layer,
lost when its CSS was converted to Tailwind, which was silently blocking
clicks on the backdrop meant to dismiss the overlay.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6a086ba to
c71f3ad
Compare
PersonBadge/PersonBadgeProgress ids were just the badge-type id (e.g.
first-signin-location-), which is shared by every member
who first signed in at that location. Since Relay normalizes its store
by raw id, responses for different members collided and silently
clobbered each other's awardedAt (and, once badgeId was split out,
PersonBadge/PersonBadgeProgress collided with each other too). Scope
ids per-person and per-type ("PersonBadge::"), and
expose the badge-type id separately as badgeId for icon/type lookups.
Also includes the in-progress badge catalog/icon work: a new April
Fools' Day badge, badge digest/export fixes, and new SVG badge assets.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Members should choose which of their own locations get a weekly badge
digest email, matching the existing daily email summary pattern,
instead of an admin-wide location toggle sending to everyone with
access.
Exposes current/target counts for badges backed by a running counter
(sign-ins, category thresholds, streaks), computed from the per-location
counters already stored on the Person record, so no extra DB reads are
needed. Manual (nightly-batch/easter-egg) and fixed-date badges keep the
plain earned/locked state since they have no meaningful running total.
Renders as a compact progress bar in the shared badge panel used by the
admin member page, the badges page, and the kiosk modal.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Previously the "new badge" prompt was shown whenever the member's home
location had gamification enabled, regardless of whether the location
they were currently scanning at had it disabled. Now the celebration
is only shown when the current location has gamification enabled;
pending awards from any location are left undisplayed (badges are
global to the person, not scoped to a location) so they surface next
time the member is somewhere gamification is on, deduped so a badge
independently re-triggered at a second location doesn't double-pop.
Also fixes a passport (first-signin-at-location) badge showing the
wrong location name/description once it could surface at a location
other than where it was earned.
Give the location "first sign-in" badge icon actual stamp iconography
(worn double ring, star emblem, ribboned unit text, tilted impression)
instead of plain concentric circles with a stray unused ring.
Replace the two crossed lines and dot placeholder with a recognizable
wrench glyph.
Replace the plain filled circle placeholder with a folded map, dotted
trail, and X marking the spot.
Extract the tier key/CSS-class/label helpers out of
MemberBadgeProgressPanel into a shared lib/badgeTiers module, and use
them in the members list to replace the single badge-count-plus-last-
earned cell with a breakdown of counts per tier (gold/silver/bronze/
starter/passport stamps).
Add a tierCounts field to the badgeLeaderboard GraphQL entry, grouping
each person's awarded badges by tier. Use it in the kiosk leaderboard
to show a per-tier pill breakdown on each row and an aggregate
breakdown in the summary bar, instead of a single opaque total.
createPeriod bypassed badge progress entirely since only scan_register2/
scan_sign_out called apply_badge_event. Now createPeriod feeds a synthetic
check-in/sign-out pair through the same counters/streaks/easter-egg logic,
using the period's own start/end time rather than wall-clock time (so
backdated entries evaluate holiday/streak rules against when the period
actually happened). Any award earned this way is left undisplayed so it
surfaces on the member's next real kiosk visit rather than being silently
consumed with no UI to show it.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
The gamification branch predated the full CSS->Tailwind conversion on
main and kept adding to style.css/kiosk.css/global.css. Those files
were resurrected during the rebase so history would apply cleanly;
this converts their remaining rules (badge cards, progress bars, kiosk
leaderboard/member-badges modals, celebration overlay, tier pills) to
Tailwind utility classes and deletes the legacy stylesheets.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
so it doesn't butt up against the member list.
instead of relying on inherited page text-align, so kiosk and admin render
it the same way.
lost when its CSS was converted to Tailwind, which was silently blocking
clicks on the backdrop meant to dismiss the overlay.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com