From 3032d76e43f67b441d9dc09a992cd44069814998 Mon Sep 17 00:00:00 2001 From: gibbyb Date: Fri, 5 Sep 2025 16:07:38 -0500 Subject: [PATCH] Weird stopping point but whatever --- convex/statuses.ts | 95 ++++++++++++++----- src/app/(auth)/signin/page.tsx | 2 +- .../layout/profile/reset-password.tsx | 2 +- .../layout/status/history/index.tsx | 7 +- src/components/layout/status/list/index.tsx | 34 ++++--- src/lib/utils.ts | 4 + 6 files changed, 101 insertions(+), 43 deletions(-) diff --git a/convex/statuses.ts b/convex/statuses.ts index fb555f0..9d9c505 100644 --- a/convex/statuses.ts +++ b/convex/statuses.ts @@ -26,6 +26,12 @@ type StatusRow = { } | null, }; +type Paginated = { + page: T[], + isDone: boolean, + continueCursor: string | null, +}; + // CHANGED: typed helpers const ensureUser = async (ctx: RWCtx, userId: Id<'users'>) => { const user = await ctx.db.get(userId); @@ -228,35 +234,76 @@ export const getCurrentForAll = query({ }); /** - * Paginated history for a specific user (newest first). + * Paginated history for all users or for a specific user. */ -export const listHistoryByUser = query({ +export const listHistory = query({ args: { - userId: v.id('users'), + userId: v.optional(v.id('users')), paginationOpts: paginationOptsValidator, }, - handler: async (ctx, { userId, paginationOpts }) => { - await ensureUser(ctx, userId); + handler: async (ctx, { userId, paginationOpts }): Promise< + Paginated + > => { + // Query statuses newest-first, optionally filtered by user + const result = userId + ? await ctx.db + .query('statuses') + .withIndex('by_user_updatedAt', (q) => q.eq('userId', userId)) + .order('desc') + .paginate(paginationOpts) + : await ctx.db + .query('statuses') + .order('desc') + .paginate(paginationOpts); - return await ctx.db - .query('statuses') - .withIndex('by_user_updatedAt', (q) => q.eq('userId', userId)) - .order('desc') - .paginate(paginationOpts); + // Cache user display objects to avoid refetching repeatedly + const displayCache = new Map(); + + const getDisplay = async ( + uid: Id<'users'>, + ): Promise => { + const key = uid as unknown as string; + const cached = displayCache.get(key); + if (cached) return cached; + + const user = await ctx.db.get(uid); + if (!user) throw new ConvexError('User not found.'); + + const imgId = getImageId(user); + const imgUrl = imgId ? await ctx.storage.getUrl(imgId) : null; + + const display: StatusRow['user'] = { + id: user._id, + name: getName(user), + imageUrl: imgUrl, + }; + displayCache.set(key, display); + return display; + }; + + const page: StatusRow[] = []; + for (const s of result.page) { + const owner = await getDisplay(s.userId); + const updatedBy = + s.updatedBy !== s.userId ? await getDisplay(s.updatedBy) : null; + + page.push({ + user: owner, + status: { + id: s._id, + message: s.message, + updatedAt: s.updatedAt, + updatedBy, + }, + }); + } + + return { + page, + isDone: result.isDone, + continueCursor: result.continueCursor, + }; }, }); -/** - * Global paginated history (all users, newest first). - * - Add an index on updatedAt if you want to avoid full-table scans - * when the collection grows large. - */ -export const listHistoryAll = query({ - args: { paginationOpts: paginationOptsValidator }, - handler: async (ctx, { paginationOpts }) => { - return await ctx.db - .query('statuses') - .order('desc') - .paginate(paginationOpts); - }, -}); + diff --git a/src/app/(auth)/signin/page.tsx b/src/app/(auth)/signin/page.tsx index 031fa0c..33e3668 100644 --- a/src/app/(auth)/signin/page.tsx +++ b/src/app/(auth)/signin/page.tsx @@ -24,7 +24,7 @@ import { TabsTrigger, } from '@/components/ui'; import { toast } from 'sonner'; -import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '~/convex/auth'; +import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/utils'; const signInFormSchema = z.object({ email: z.email({ diff --git a/src/components/layout/profile/reset-password.tsx b/src/components/layout/profile/reset-password.tsx index ea025fa..6fb5316 100644 --- a/src/components/layout/profile/reset-password.tsx +++ b/src/components/layout/profile/reset-password.tsx @@ -21,7 +21,7 @@ import { SubmitButton, } from '@/components/ui'; import { toast } from 'sonner'; -import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '~/convex/auth'; +import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/utils'; const formSchema = z .object({ diff --git a/src/components/layout/status/history/index.tsx b/src/components/layout/status/history/index.tsx index 494c083..d713bed 100644 --- a/src/components/layout/status/history/index.tsx +++ b/src/components/layout/status/history/index.tsx @@ -29,15 +29,14 @@ import { import { toast } from 'sonner'; type StatusHistoryProps = { - preloadedUser: Preloaded; + userId?: Id<'users'>; }; export const StatusHistory = ({ - preloadedUser, + userId, }: StatusHistoryProps) => { - const user = usePreloadedQuery(preloadedUser); return ( - +
); }; diff --git a/src/components/layout/status/list/index.tsx b/src/components/layout/status/list/index.tsx index 035a758..b0aca43 100644 --- a/src/components/layout/status/list/index.tsx +++ b/src/components/layout/status/list/index.tsx @@ -18,6 +18,7 @@ import { import { toast } from 'sonner'; import { ccn, formatTime, formatDate } from '@/lib/utils'; import { Clock, Calendar, CheckCircle2 } from 'lucide-react'; +import { StatusHistory } from '@/components/layout/status'; type StatusListProps = { preloadedUser: Preloaded; @@ -36,6 +37,7 @@ export const StatusList = ({ const [selectAll, setSelectAll] = useState(false); const [statusInput, setStatusInput] = useState(''); const [updatingStatus, setUpdatingStatus] = useState(false); + const [selectedHistoryUserId, setSelectedHistoryUserId] = useState>(); const bulkCreate = useMutation(api.statuses.bulkCreate); @@ -155,19 +157,25 @@ export const StatusList = ({ )}
-
- -
+ + +
setSelectedHistoryUserId(u.id)} + > + +
+
+ {selectedHistoryUserId === u.id && ( + + )} +
diff --git a/src/lib/utils.ts b/src/lib/utils.ts index fdee135..c35ee5e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -19,6 +19,10 @@ export const ccn = ({ return twMerge(className, context ? on : off); }; +export const PASSWORD_MIN = 8; +export const PASSWORD_MAX = 100; +export const PASSWORD_REGEX = /^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u; + type Timestamp = number | string | Date; const toDate = (ts: Timestamp): Date | null => {