Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/community-mobile/app/auth/otp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRouter, useLocalSearchParams } from "expo-router"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import Ionicons from "@expo/vector-icons/Ionicons"
import {
EMAIL_OTP_CODE_LENGTH,
REVIEWER_OTP_CODE_MAX_LENGTH,
REVIEWER_OTP_CODE_MIN_LENGTH,
REVIEWER_OTP_EMAIL,
Expand Down Expand Up @@ -32,10 +33,9 @@ import { useResendCooldown } from "@/hooks/useResendCooldown"
const { ScrollView: OtpScrollView } = makeKeyboardAwareScrollHost(PLAIN_SCROLL_HOST)

const MAX_ATTEMPTS = 3
const CODE_LENGTH = 6

function isCodeIncomplete(code: string, reviewer: boolean): boolean {
return reviewer ? code.trim().length < REVIEWER_OTP_CODE_MIN_LENGTH : code.length !== CODE_LENGTH
return reviewer ? code.trim().length < REVIEWER_OTP_CODE_MIN_LENGTH : code.length !== EMAIL_OTP_CODE_LENGTH
}

export default function OtpScreen() {
Expand Down Expand Up @@ -194,7 +194,7 @@ export default function OtpScreen() {
setError(null)
setCode(next)
}}
length={CODE_LENGTH}
length={EMAIL_OTP_CODE_LENGTH}
onComplete={onVerify}
editable={!locked && !verifying}
/>
Expand Down
4 changes: 1 addition & 3 deletions apps/community-mobile/src/components/FirstRunGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"
import { View, StyleSheet, ActivityIndicator, Pressable } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import Ionicons from "@expo/vector-icons/Ionicons"
import { HANDLE_MAX_LENGTH } from "@civfix/shared"
import { MIN_TOUCH_TARGET, makeThemedStyles, space, useTheme } from "@/theme"
import {
AgeConfirmation,
Expand Down Expand Up @@ -32,9 +33,6 @@ import { friendlyError } from "@/lib/errors"

const { ScrollView: FirstRunScrollView } = makeKeyboardAwareScrollHost(PLAIN_SCROLL_HOST)

// The upper bound of the shared HANDLE_REGEX, so the field stops where validation would reject.
const HANDLE_MAX_LENGTH = 20

export function FirstRunGate() {
const status = useAuthStore((s) => s.status)
const incomplete = useAuthStore((s) => s.user?.profileComplete === false)
Expand Down
4 changes: 3 additions & 1 deletion apps/community-mobile/src/lib/authResume.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { stripTrailingSlashes } from "@civfix/shared"

const ROOT_ROUTE = "/"

function routeQuery(params: unknown): string {
Expand Down Expand Up @@ -27,7 +29,7 @@ export function resumePathname(href: string): string {
const cut = href.search(/[?#]/)
const path = cut === -1 ? href : href.slice(0, cut)
if (path.length > 1 && path.endsWith("/")) {
const trimmed = path.replace(/\/+$/, "")
const trimmed = stripTrailingSlashes(path)
return trimmed.length > 0 ? trimmed : "/"
}
return path
Expand Down
7 changes: 3 additions & 4 deletions apps/community-web/src/components/auth/auth-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from "react"
import { createPortal } from "react-dom"
import { X } from "lucide-react"

import { EMAIL_OTP_CODE_LENGTH } from "@civfix/shared"
import { useT } from "@civfix/ui/i18n"

import { useFocusTrap } from "@/components/console/overlay/use-focus-trap"
Expand All @@ -18,8 +19,6 @@ import { useUiStore } from "@/store/ui-store"
import { AuthStepHeading, ChoicesStep, CodeStep, EmailStep, type AuthStep } from "./auth-modal-steps"
import { useOtpEntry } from "./use-otp-entry"

const OTP_LENGTH = 6

/**
* The return target is the current origin, or a same-origin console path allowlisted by oauthReturnPath
* (such as an org invite waiting to be accepted). Apple is offered only when the server has a Sign in
Expand Down Expand Up @@ -51,7 +50,7 @@ export function AuthModal({ oauthReturnPath = null }: AuthModalProps = {}) {

const [step, setStep] = React.useState<AuthStep>("choices")
const [email, setEmail] = React.useState("")
const otp = useOtpEntry(OTP_LENGTH)
const otp = useOtpEntry(EMAIL_OTP_CODE_LENGTH)
const [submitting, setSubmitting] = React.useState(false)
const [error, setError] = React.useState<string | null>(null)
const [resendAfter, setResendAfter] = React.useState<number>(0)
Expand Down Expand Up @@ -222,7 +221,7 @@ export function AuthModal({ oauthReturnPath = null }: AuthModalProps = {}) {
<CodeStep
t={t}
otp={otp}
length={OTP_LENGTH}
length={EMAIL_OTP_CODE_LENGTH}
submitting={submitting}
error={error}
resendAfter={resendAfter}
Expand Down
23 changes: 11 additions & 12 deletions apps/community-web/src/components/dev/bodies-gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
PostDTO,
} from "@civfix/shared"
import type { CleanupAttendeesResponse } from "@civfix/shared"
import { MS_PER_DAY, MS_PER_MINUTE } from "@civfix/shared"
import type {
EventHoursResponse,
EventSlotDTO,
Expand Down Expand Up @@ -79,14 +80,12 @@ import {
portfolioOverrides,
} from "./dashboard-fixtures"
import {
DAY_MS,
GALLERY_LEADERBOARD_PODIUM,
GALLERY_MY_PROFILE,
GALLERY_NOTIFICATIONS,
GALLERY_NOTIFICATION_PREFS,
GALLERY_SEARCH_RESULTS,
GALLERY_VIEWER,
MINUTE_MS,
daysAgo,
galleryChatHistory,
galleryThreads,
Expand All @@ -108,7 +107,7 @@ const ORGANIZER = {
}

function event(id: string, title: string, daysAgo: number, organizerId: string): CleanupDTO {
const scheduledAt = isoFromNow(-daysAgo * DAY_MS)
const scheduledAt = isoFromNow(-daysAgo * MS_PER_DAY)
return {
id,
title,
Expand Down Expand Up @@ -166,7 +165,7 @@ const LINKED_EVENTS = [
id: "e1",
title: "Creekside litter sweep",
eventKind: "cleanup",
scheduledAt: isoFromNow(2 * DAY_MS),
scheduledAt: isoFromNow(2 * MS_PER_DAY),
lat: 37.77,
lng: -122.42,
going: 8,
Expand Down Expand Up @@ -264,7 +263,7 @@ const NEARBY_CLEANUPS: { items: CleanupDTO[]; nextCursor: string | null } = {
items: [
{
...event("e1", "Creekside litter sweep", -2, "p-ann"),
scheduledAt: isoFromNow(2 * DAY_MS),
scheduledAt: isoFromNow(2 * MS_PER_DAY),
status: "upcoming",
joined: true,
going: 8,
Expand All @@ -273,7 +272,7 @@ const NEARBY_CLEANUPS: { items: CleanupDTO[]; nextCursor: string | null } = {
{
...event("e2", "Mission mural touch-up", -2, "p-lee"),
organizer: { ...ORGANIZER, id: "p-lee", name: "Lee Tran", handle: "leetran" },
scheduledAt: isoFromNow(5 * DAY_MS),
scheduledAt: isoFromNow(5 * MS_PER_DAY),
status: "upcoming",
joined: false,
going: 14,
Expand All @@ -282,15 +281,15 @@ const NEARBY_CLEANUPS: { items: CleanupDTO[]; nextCursor: string | null } = {
{
...event("e5", "Dolores Park planting", -2, "p-mei"),
organizer: { ...ORGANIZER, id: "p-mei", name: "Mei Wong", handle: "meiwong" },
scheduledAt: isoFromNow(6 * DAY_MS),
scheduledAt: isoFromNow(6 * MS_PER_DAY),
status: "upcoming",
joined: false,
going: 5,
dist: 1.1,
},
{
...event("e6", "Bayview shoreline sweep", -2, "p-ann"),
scheduledAt: isoFromNow(8 * DAY_MS),
scheduledAt: isoFromNow(8 * MS_PER_DAY),
status: "upcoming",
joined: false,
going: 21,
Expand Down Expand Up @@ -322,7 +321,7 @@ const FEED_POSTS: PostDTO[] = [
author: ORGANIZER,
kind: "post",
body: "We are meeting by the east gate Saturday morning. Come help us reset the creek path before summer.",
createdAt: isoFromNow(-35 * MINUTE_MS),
createdAt: isoFromNow(-35 * MS_PER_MINUTE),
event: {
id: "e1",
title: "Creekside litter sweep",
Expand All @@ -333,7 +332,7 @@ const FEED_POSTS: PostDTO[] = [
lng: NEARBY_CLEANUPS.items[0]!.lng,
going: 8,
organizer: ORGANIZER,
linkedAt: isoFromNow(-35 * MINUTE_MS),
linkedAt: isoFromNow(-35 * MS_PER_MINUTE),
},
report: null,
repostOf: null,
Expand Down Expand Up @@ -412,7 +411,7 @@ const CLEANUP_DETAIL: CleanupDTO = {
eventKind: "cleanup",
description:
"Join us for a morning sweep along the creek path. We will tackle the litter that washed up after the rain, then grab coffee. Newcomers welcome - tools provided if you do not have your own.",
scheduledAt: isoFromNow(2 * DAY_MS),
scheduledAt: isoFromNow(2 * MS_PER_DAY),
status: "upcoming",
joined: false,
going: 8,
Expand Down Expand Up @@ -440,7 +439,7 @@ const EDIT_CLEANUP_DETAIL: CleanupDTO = {
...event("e-mine", "24th St planter day", -3, "me"),
eventKind: "cleanup",
description: "We are refreshing the sidewalk planters along 24th St. Tools and soil provided.",
scheduledAt: isoFromNow(6 * DAY_MS),
scheduledAt: isoFromNow(6 * MS_PER_DAY),
status: "upcoming",
joined: false,
going: 5,
Expand Down
46 changes: 23 additions & 23 deletions apps/community-web/src/components/dev/dashboard-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
fakeHostedEvents,
fakeHostedEventsAnalytics,
} from "@civfix/shared/fakes"
import { MAX_PORTFOLIO_TOP_VOLUNTEERS } from "@civfix/shared"
import { MAX_PORTFOLIO_TOP_VOLUNTEERS, MS_PER_DAY, MS_PER_HOUR, MS_PER_MINUTE } from "@civfix/shared"
import type {
CleanupDTO,
EventCheckinCountersDTO,
Expand All @@ -26,7 +26,7 @@ import type {
PortfolioAnalyticsRange,
} from "@civfix/shared"

import { DAY_MS, HOUR_MS, MINUTE_MS, makeCannedApi, type FakeEndpoint } from "./fixtures"
import { makeCannedApi, type FakeEndpoint } from "./fixtures"

const FIXTURE_NOW = Date.now()

Expand Down Expand Up @@ -114,8 +114,8 @@ const DASHBOARD_ORGS: readonly OrganizationDTO[] = [
socialLinks: null,
verifiedStatus: "verified",
verifiedKind: "nonprofit",
verifiedAt: new Date(FIXTURE_NOW - 240 * DAY_MS).toISOString(),
createdAt: new Date(FIXTURE_NOW - 500 * DAY_MS).toISOString(),
verifiedAt: new Date(FIXTURE_NOW - 240 * MS_PER_DAY).toISOString(),
createdAt: new Date(FIXTURE_NOW - 500 * MS_PER_DAY).toISOString(),
memberCount: 24,
eventCount: 31,
myRole: "owner",
Expand All @@ -134,7 +134,7 @@ const DASHBOARD_ORGS: readonly OrganizationDTO[] = [
verifiedStatus: "unverified",
verifiedKind: null,
verifiedAt: null,
createdAt: new Date(FIXTURE_NOW - 180 * DAY_MS).toISOString(),
createdAt: new Date(FIXTURE_NOW - 180 * MS_PER_DAY).toISOString(),
memberCount: 9,
eventCount: 6,
myRole: "admin",
Expand All @@ -151,15 +151,15 @@ const EVENT_INVITES: ListMyEventInvitesResponse = {
event: {
id: "ev-invite-1",
title: "Creekside trail restoration",
startsAt: new Date(FIXTURE_NOW + 6 * DAY_MS).toISOString(),
endsAt: new Date(FIXTURE_NOW + 6 * DAY_MS + 4 * HOUR_MS).toISOString(),
startsAt: new Date(FIXTURE_NOW + 6 * MS_PER_DAY).toISOString(),
endsAt: new Date(FIXTURE_NOW + 6 * MS_PER_DAY + 4 * MS_PER_HOUR).toISOString(),
status: "upcoming",
coverThumbUrl: null,
address: "Glen Canyon Park, San Francisco",
},
invitedBy: COHOST,
createdAt: new Date(FIXTURE_NOW - 2 * DAY_MS).toISOString(),
expiresAt: new Date(FIXTURE_NOW + 12 * DAY_MS).toISOString(),
createdAt: new Date(FIXTURE_NOW - 2 * MS_PER_DAY).toISOString(),
expiresAt: new Date(FIXTURE_NOW + 12 * MS_PER_DAY).toISOString(),
},
],
nextCursor: null,
Expand All @@ -179,20 +179,20 @@ const ORG_INVITES: ListMyOrgInvitesResponse = {
},
role: "admin",
invitedBy: COHOST,
createdAt: new Date(FIXTURE_NOW - 4 * DAY_MS).toISOString(),
expiresAt: new Date(FIXTURE_NOW + 10 * DAY_MS).toISOString(),
createdAt: new Date(FIXTURE_NOW - 4 * MS_PER_DAY).toISOString(),
expiresAt: new Date(FIXTURE_NOW + 10 * MS_PER_DAY).toISOString(),
},
],
}

const ORG_MEMBERS: ListOrganizationMembersResponse = {
items: [
{ person: HOST, role: "owner", joinedAt: new Date(FIXTURE_NOW - 500 * DAY_MS).toISOString(), canRemove: false },
{ person: COHOST, role: "admin", joinedAt: new Date(FIXTURE_NOW - 300 * DAY_MS).toISOString(), canRemove: true },
{ person: HOST, role: "owner", joinedAt: new Date(FIXTURE_NOW - 500 * MS_PER_DAY).toISOString(), canRemove: false },
{ person: COHOST, role: "admin", joinedAt: new Date(FIXTURE_NOW - 300 * MS_PER_DAY).toISOString(), canRemove: true },
{
person: person("p-lee", "Lee Tran", "leetran"),
role: "member",
joinedAt: new Date(FIXTURE_NOW - 90 * DAY_MS).toISOString(),
joinedAt: new Date(FIXTURE_NOW - 90 * MS_PER_DAY).toISOString(),
canRemove: true,
},
],
Expand All @@ -209,8 +209,8 @@ const ORG_INVITE_LIST: ListOrganizationInvitesResponse = {
role: "member",
status: "pending",
invitedBy: HOST,
createdAt: new Date(FIXTURE_NOW - 3 * DAY_MS).toISOString(),
expiresAt: new Date(FIXTURE_NOW + 11 * DAY_MS).toISOString(),
createdAt: new Date(FIXTURE_NOW - 3 * MS_PER_DAY).toISOString(),
expiresAt: new Date(FIXTURE_NOW + 11 * MS_PER_DAY).toISOString(),
},
],
}
Expand Down Expand Up @@ -239,8 +239,8 @@ const EXTRA_TOP_VOLUNTEERS: readonly LeaderboardEntryDTO[] = [
const NEEDS_HOURS_EVENT: HostedEventDTO = {
id: "ev-needs-hours",
title: "Islais Creek weed pull",
startsAt: new Date(FIXTURE_NOW - 2 * DAY_MS).toISOString(),
endsAt: new Date(FIXTURE_NOW - 2 * DAY_MS + 3 * HOUR_MS).toISOString(),
startsAt: new Date(FIXTURE_NOW - 2 * MS_PER_DAY).toISOString(),
endsAt: new Date(FIXTURE_NOW - 2 * MS_PER_DAY + 3 * MS_PER_HOUR).toISOString(),
timezone: "America/Los_Angeles",
status: "upcoming",
visibility: "public",
Expand Down Expand Up @@ -368,7 +368,7 @@ function phaseCounters(id: string, phase: EventPhase): EventCheckinCountersDTO {
capacity: row.capacity,
})),
arrivals: insights.arrivals.map((bucket) => ({
at: new Date(startsAt + bucket.offsetMin * MINUTE_MS).toISOString(),
at: new Date(startsAt + bucket.offsetMin * MS_PER_MINUTE).toISOString(),
count: bucket.seats,
})),
asOf: new Date().toISOString(),
Expand Down Expand Up @@ -407,18 +407,18 @@ function phaseRoster(id: string, phase: EventPhase): ListEventRegistrationsRespo
attendeeName: seatIndex === 0 ? who.name : null,
status: "active" as const,
ticketToken: null,
checkedInAt: checkedIn ? new Date(startsAt + i * 6 * MINUTE_MS).toISOString() : null,
checkedInAt: checkedIn ? new Date(startsAt + i * 6 * MS_PER_MINUTE).toISOString() : null,
checkinMethod: checkedIn ? ("scan" as const) : null,
noShowAt: null,
})),
status: i === ROSTER_PEOPLE.length - 1 ? "cancelled" : "registered",
source: i === 3 ? "walkup" : "self",
registeredAt: new Date(startsAt - (14 - i) * DAY_MS).toISOString(),
registeredAt: new Date(startsAt - (14 - i) * MS_PER_DAY).toISOString(),
cancelledAt:
i === ROSTER_PEOPLE.length - 1
? new Date(startsAt - 2 * DAY_MS).toISOString()
? new Date(startsAt - 2 * MS_PER_DAY).toISOString()
: null,
checkedInAt: checkedIn ? new Date(startsAt + i * 6 * MINUTE_MS).toISOString() : null,
checkedInAt: checkedIn ? new Date(startsAt + i * 6 * MS_PER_MINUTE).toISOString() : null,
checkedInBy: checkedIn ? HOST : null,
slot: null,
waitlistPosition: null,
Expand Down
11 changes: 4 additions & 7 deletions apps/community-web/src/components/dev/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ import type {
UserDTO,
UserProfileDTO,
} from "@civfix/shared"

export const MINUTE_MS = 60_000
export const HOUR_MS = 60 * MINUTE_MS
export const DAY_MS = 24 * HOUR_MS
import { MS_PER_DAY, MS_PER_HOUR, MS_PER_MINUTE } from "@civfix/shared"

export const isoFromNow = (ms: number): string => new Date(Date.now() + ms).toISOString()
export const hoursAgo = (h: number): string => isoFromNow(-h * HOUR_MS)
export const daysAgo = (d: number): string => isoFromNow(-d * DAY_MS)
export const hoursAgo = (h: number): string => isoFromNow(-h * MS_PER_HOUR)
export const daysAgo = (d: number): string => isoFromNow(-d * MS_PER_DAY)

export const GALLERY_VIEWER = {
id: "me",
Expand Down Expand Up @@ -139,7 +136,7 @@ function chatMessage(
},
body,
kind: "text",
createdAt: isoFromNow(-minsAgo * MINUTE_MS),
createdAt: isoFromNow(-minsAgo * MS_PER_MINUTE),
} as ChatMessageDTO
}

Expand Down
4 changes: 2 additions & 2 deletions apps/community-web/src/components/dev/landscape-fake-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import type {
ReportDTO,
UserProfileDTO,
} from "@civfix/shared"
import { MS_PER_DAY } from "@civfix/shared"
import { makeFakeApiClient } from "@civfix/ui/data"

import {
DAY_MS,
GALLERY_LEADERBOARD_PODIUM,
GALLERY_MY_PROFILE,
GALLERY_NOTIFICATIONS,
Expand Down Expand Up @@ -77,7 +77,7 @@ function cleanup(
description: "Gloves, bags and grabbers provided. Meet at the gate; we finish by noon.",
lat: 37.77,
lng: -122.42,
scheduledAt: isoFromNow(inDays * DAY_MS),
scheduledAt: isoFromNow(inDays * MS_PER_DAY),
status: "upcoming",
organizer,
going: 12,
Expand Down
Loading