Skip to content

fix: Payments page with history navigation, month filter, and progress bar#140

Merged
jhechtf merged 1 commit intomainfrom
fix/payments-page
Apr 5, 2026
Merged

fix: Payments page with history navigation, month filter, and progress bar#140
jhechtf merged 1 commit intomainfrom
fix/payments-page

Conversation

@jhechtf
Copy link
Copy Markdown
Contributor

@jhechtf jhechtf commented Apr 4, 2026

Summary by CodeRabbit

  • New Features

    • Month selector added to view historical payments for different time periods
    • Payments now grouped by household for improved organization
    • Monthly Progress section introduced with visual payment status indicators
    • Visual overdue indicators and "Due by" dates added to payment items
  • Bug Fixes

    • Fixed payment filtering to correctly account for both month and year

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bill-tracker-mono Ready Ready Preview, Comment Apr 5, 2026 8:04am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 4, 2026

Warning

Rate limit exceeded

@jhechtf has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 39 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 25 minutes and 39 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 45d3312d-1003-43d5-b658-5d3d5197c20e

📥 Commits

Reviewing files that changed from the base of the PR and between e2bd9f1 and d97bf99.

📒 Files selected for processing (4)
  • apps/website/src/app.css
  • apps/website/src/lib/remotes/dashboard.remote.ts
  • apps/website/src/lib/remotes/payments.remote.ts
  • apps/website/src/routes/dashboard/payments/+page.svelte
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/payments-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor Author

jhechtf commented Apr 4, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
apps/website/src/lib/remotes/payments.remote.ts (1)

62-75: Prefer a month range over extract() on forMonthD.

Wrapping the column in extract() makes this new hot path much harder to satisfy from an index on forMonthD. A [monthStart, nextMonthStart) predicate will scale better here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/lib/remotes/payments.remote.ts` around lines 62 - 75,
Replace the extract() predicates on schema.payments.forMonthD with a half-open
range on the date column: compute a monthStart (first day of isoMonth/isoYear)
and nextMonthStart (first day of the following month) from isoMonth and isoYear,
then replace the two eq(sql`extract(...)`) clauses with comparisons
gte(schema.payments.forMonthD, monthStart) and lt(schema.payments.forMonthD,
nextMonthStart); keep the inArray filter on userHouseholds.map((h) => h.id)
unchanged. Ensure monthStart/nextMonthStart are proper Date or ISO date strings
matching the column type so the DB can use an index on forMonthD.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/src/lib/remotes/payments.remote.ts`:
- Around line 13-17: The optionalISODateValidator currently only checks format
and allows impossible dates (e.g., 2026-13-40) which later cause new
Date(...).toISOString() to throw; update the validator
(optionalISODateValidator) to add a runtime refinement that, for non-undefined
and non-Date string values, constructs a Date and verifies it's valid (e.g., new
Date(value) produces a date with !isNaN(date.getTime())) before accepting, and
similarly ensure any branch that accepts Date objects checks date.getTime() is
not NaN; apply the same refinement to the other validator in this file that uses
the same regex so invalid calendar dates are rejected at validation time rather
than causing a RangeError later.
- Around line 19-40: Mutations togglePayment, uploadImage, markPayment, and
unmarkPayment only invalidate getCurrentPayments() and getPayment(), leaving
getCurrentPaymentsByHousehold(date) cached per-argument and stale; update each
mutation to also refresh the grouped query by either accepting the date argument
and calling the remote refresh for getCurrentPaymentsByHousehold(date) after
success, or (if you can't pass date) call your cache/remote invalidation that
targets all keys for getCurrentPaymentsByHousehold so every date-argument entry
is invalidated; locate these changes in the mutation handlers for togglePayment,
uploadImage, markPayment, and unmarkPayment and ensure they reference
getCurrentPaymentsByHousehold when triggering refreshes.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte:
- Around line 105-108: The displayed month/day is using
selectedMonth.toLocaleDateString without specifying timeZone which causes US
timezones to render the previous day/month; update the toLocaleDateString calls
that render selectedMonth (and the similar call at the other occurrence) to pass
timeZone: 'UTC' in the options (e.g.,
selectedMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric',
timeZone: 'UTC' })) so the UI matches the UTC-normalized selectedMonth and the
date-only forMonthD values.
- Around line 141-143: The comparison uses payment.forMonthD < new Date() which
compares a date-only value to a timestamp and makes bills due today appear
overdue; update the checks that set 'variant-outline-error' (the expressions
using payment.forMonthD < new Date() at the shown condition and the similar
occurrences at lines 153-154) to compare only the date portions (e.g., normalize
both sides to start-of-day or compare formatted yyyy-mm-dd / toDateString()) so
that payments due today are not treated as past due—apply the same normalization
in every place that currently does payment.forMonthD < new Date().
- Around line 30-35: paymentHistoryMonths is used without guarding for an empty
array causing paymentHistoryMonths[0].month and selectedMonth.toISOString() to
throw; modify the initialization so selectedMonth is derived defensively from
paymentHistoryMonths (e.g., selectedMonth = $derived(paymentHistoryMonths, p =>
p.length ? p[0].month : null) or default to new Date()), and update
paymentsByHousehold to only call
getCurrentPaymentsByHousehold(selectedMonth.toISOString()) when selectedMonth is
non-null (or pass a safe default), referencing the symbols paymentHistoryMonths,
selectedMonth, paymentsByHousehold, getPaymentHistoryMonths, and
getCurrentPaymentsByHousehold.
- Around line 117-120: The Progress bar is being sized against the current month
(today/new Date()) instead of the filtered month; update the Progress props to
use the selectedMonth range so pins scale correctly — pass today as the
selectedMonth (or new Date(selectedMonth) if it's a string) and set end to
getLastDayOfMonth(selectedMonth) (keep pins={paymentPins} and the Progress
component name the same).

---

Nitpick comments:
In `@apps/website/src/lib/remotes/payments.remote.ts`:
- Around line 62-75: Replace the extract() predicates on
schema.payments.forMonthD with a half-open range on the date column: compute a
monthStart (first day of isoMonth/isoYear) and nextMonthStart (first day of the
following month) from isoMonth and isoYear, then replace the two
eq(sql`extract(...)`) clauses with comparisons gte(schema.payments.forMonthD,
monthStart) and lt(schema.payments.forMonthD, nextMonthStart); keep the inArray
filter on userHouseholds.map((h) => h.id) unchanged. Ensure
monthStart/nextMonthStart are proper Date or ISO date strings matching the
column type so the DB can use an index on forMonthD.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1626a4f2-7823-4ff1-ad4b-0855d8fd8007

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2d55d and d09d07e.

📒 Files selected for processing (2)
  • apps/website/src/lib/remotes/payments.remote.ts
  • apps/website/src/routes/dashboard/payments/+page.svelte

Comment on lines +13 to +17
const optionalISODateValidator = type(
/^\d{4}-\d{2}-\d{2}((?:T|\s)\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/,
)
.or('undefined')
.or('Date');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject impossible dates before calling toISOString().

The regex accepts structurally valid but impossible values like 2026-13-40. new Date(date).toISOString() then throws a RangeError, so bad input becomes a 500 instead of a validation failure.

🛡️ Proposed fix
 export const getCurrentPayments = query(
   optionalISODateValidator,
   async (date) => {
     const userHouseholds = await getUserHouseholds();
-    const isoDate = (date ? new Date(date) : new Date()).toISOString();
+    const parsedDate = date ? new Date(date) : new Date();
+    if (Number.isNaN(parsedDate.getTime())) {
+      throw error(400, 'Invalid date');
+    }
+    const isoDate = parsedDate.toISOString();
     const [isoYear, isoMonth] = isoDate.split('T')[0].split('-').map(Number);

Also applies to: 47-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/lib/remotes/payments.remote.ts` around lines 13 - 17, The
optionalISODateValidator currently only checks format and allows impossible
dates (e.g., 2026-13-40) which later cause new Date(...).toISOString() to throw;
update the validator (optionalISODateValidator) to add a runtime refinement
that, for non-undefined and non-Date string values, constructs a Date and
verifies it's valid (e.g., new Date(value) produces a date with
!isNaN(date.getTime())) before accepting, and similarly ensure any branch that
accepts Date objects checks date.getTime() is not NaN; apply the same refinement
to the other validator in this file that uses the same regex so invalid calendar
dates are rejected at validation time rather than causing a RangeError later.

Comment on lines +19 to +40
export const getCurrentPaymentsByHousehold = query(
optionalISODateValidator,
async (date) => {
const payments = await getCurrentPayments(date);
const grouped: Record<string, { name: string; payments: typeof payments }> =
payments.reduce(
(acc, payment) => {
if (!acc[payment.householdId]) {
acc[payment.householdId] = {
name: payment.household.name,
payments: [],
};
}

acc[payment.householdId].payments.push(payment);

return acc;
},
{} as Record<string, { name: string; payments: typeof payments }>,
);
return grouped;
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In the Svelte remote query API used by $app/server, if query A (getCurrentPaymentsByHousehold(date)) awaits query B (getCurrentPayments(date)), does calling getCurrentPayments().refresh() invalidate active subscriptions to query A, or must query A and its specific arguments be refreshed explicitly?

💡 Result:

Calling getCurrentPayments(date).refresh does not invalidate active subscriptions to query A (getCurrentPaymentsByHousehold(date)). Query A must be refreshed explicitly with its specific arguments, e.g. getCurrentPaymentsByHousehold(householdId, date).refresh. Remote queries in SvelteKit are cached and identified by a combination of their function name (or ID) and the stringified/encoded arguments passed to them. Different arguments result in different cache keys, even if the functions call shared downstream queries internally. Refreshing a query only invalidates and refetches that specific instance (with its arguments); it does not propagate to other queries with different arguments or names. Official docs confirm: "Any query can be re-fetched via its refresh method" with examples like getPosts.refresh, implying per-instance refresh. GitHub issues highlight the need for explicit argument specification to refresh parameterized queries, as the server cannot automatically invalidate unknown client-side instances (e.g., #14367 discusses needing keys or parameters to refresh dependents without knowing exact args). No automatic dependency invalidation exists; this is a known limitation driving feature requests for keyed invalidation like TanStack Query.

Citations:


🏁 Script executed:

cat -n apps/website/src/lib/remotes/payments.remote.ts

Repository: sungmanito/mono

Length of output: 12178


🏁 Script executed:

rg "getCurrentPaymentsByHousehold" --type ts --type js

Repository: sungmanito/mono

Length of output: 158


🏁 Script executed:

rg "getCurrentPaymentsByHousehold" --type svelte

Repository: sungmanito/mono

Length of output: 89


🏁 Script executed:

rg "getCurrentPaymentsByHousehold" -g "*.svelte" -g "*.ts" -g "*.js"

Repository: sungmanito/mono

Length of output: 375


🏁 Script executed:

cat -n apps/website/src/routes/dashboard/payments/+page.svelte | head -100

Repository: sungmanito/mono

Length of output: 3616


🏁 Script executed:

cat -n apps/website/src/routes/dashboard/payments/+page.svelte | tail -100

Repository: sungmanito/mono

Length of output: 4396


Add getCurrentPaymentsByHousehold refresh to all payment mutations.

Currently, mutations (togglePayment, uploadImage, markPayment, unmarkPayment) only refresh getCurrentPayments() and getPayment(). Since getCurrentPaymentsByHousehold(date) is cached separately by argument in SvelteKit's remote query system, the grouped payment list remains stale after these mutations. The progress bar and payment list on the payments page will show outdated data until navigation.

Update each mutation to also refresh the grouped query. Note: mutations must either accept the date as a parameter or find another way to invalidate all instances of getCurrentPaymentsByHousehold() since the cache key includes the specific date argument.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/lib/remotes/payments.remote.ts` around lines 19 - 40,
Mutations togglePayment, uploadImage, markPayment, and unmarkPayment only
invalidate getCurrentPayments() and getPayment(), leaving
getCurrentPaymentsByHousehold(date) cached per-argument and stale; update each
mutation to also refresh the grouped query by either accepting the date argument
and calling the remote refresh for getCurrentPaymentsByHousehold(date) after
success, or (if you can't pass date) call your cache/remote invalidation that
targets all keys for getCurrentPaymentsByHousehold so every date-argument entry
is invalidated; locate these changes in the mutation handlers for togglePayment,
uploadImage, markPayment, and unmarkPayment and ensure they reference
getCurrentPaymentsByHousehold when triggering refreshes.

Comment on lines +30 to +35
const paymentHistoryMonths = $derived(await getPaymentHistoryMonths());
let selectedMonth = $derived(paymentHistoryMonths[0].month);

const paymentsByHousehold = $derived(
await getCurrentPaymentsByHousehold(selectedMonth.toISOString()),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard the no-history path before dereferencing the first month.

If getPaymentHistoryMonths() returns [], Line 31 throws on paymentHistoryMonths[0].month and Line 34 follows with .toISOString(). Users with no payments never reach an empty state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte around lines 30 -
35, paymentHistoryMonths is used without guarding for an empty array causing
paymentHistoryMonths[0].month and selectedMonth.toISOString() to throw; modify
the initialization so selectedMonth is derived defensively from
paymentHistoryMonths (e.g., selectedMonth = $derived(paymentHistoryMonths, p =>
p.length ? p[0].month : null) or default to new Date()), and update
paymentsByHousehold to only call
getCurrentPaymentsByHousehold(selectedMonth.toISOString()) when selectedMonth is
non-null (or pass a safe default), referencing the symbols paymentHistoryMonths,
selectedMonth, paymentsByHousehold, getPaymentHistoryMonths, and
getCurrentPaymentsByHousehold.

Comment on lines +105 to +108
Showing all payments for {selectedMonth.toLocaleDateString(undefined, {
month: 'long',
year: 'numeric',
})}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Render these calendar dates in UTC.

selectedMonth is UTC-normalized in apps/website/src/lib/remotes/payments.remote.ts:335-338, and forMonthD is a date-only field in packages/db/src/tables/payments.table.ts:33. Without timeZone: 'UTC', US timezones can show the previous month/day here.

🗓️ Proposed fix
   <p class="font-xl font-semibold text-zinc-400">
     Showing all payments for {selectedMonth.toLocaleDateString(undefined, {
       month: 'long',
       year: 'numeric',
+      timeZone: 'UTC',
     })}
   </p>
@@
                   <em>
                     Due by {payment.forMonthD.toLocaleString(undefined, {
                       month: 'long',
                       day: 'numeric',
+                      timeZone: 'UTC',
                     })}
                   </em>

Also applies to: 206-209

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte around lines 105 -
108, The displayed month/day is using selectedMonth.toLocaleDateString without
specifying timeZone which causes US timezones to render the previous day/month;
update the toLocaleDateString calls that render selectedMonth (and the similar
call at the other occurrence) to pass timeZone: 'UTC' in the options (e.g.,
selectedMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric',
timeZone: 'UTC' })) so the UI matches the UTC-normalized selectedMonth and the
date-only forMonthD values.

Comment on lines +117 to +120
<Progress
today={new Date()}
end={getLastDayOfMonth(new Date())}
pins={paymentPins}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use the filtered month to size the progress bar.

The page is filtering by selectedMonth, but Line 119 still uses the current month. February and 31-day months will render pins on the wrong scale.

📊 Proposed fix
   <Progress
     today={new Date()}
-    end={getLastDayOfMonth(new Date())}
+    end={getLastDayOfMonth(selectedMonth)}
     pins={paymentPins}
   />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Progress
today={new Date()}
end={getLastDayOfMonth(new Date())}
pins={paymentPins}
<Progress
today={new Date()}
end={getLastDayOfMonth(selectedMonth)}
pins={paymentPins}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte around lines 117 -
120, The Progress bar is being sized against the current month (today/new
Date()) instead of the filtered month; update the Progress props to use the
selectedMonth range so pins scale correctly — pass today as the selectedMonth
(or new Date(selectedMonth) if it's a string) and set end to
getLastDayOfMonth(selectedMonth) (keep pins={paymentPins} and the Progress
component name the same).

Comment on lines +141 to +143
'variant-outline-success': payment.paidAt !== null,
'variant-outline-error':
payment.paidAt === null && payment.forMonthD < new Date(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Compare due dates as dates, not timestamps.

payment.forMonthD is a date-only value, so payment.forMonthD < new Date() flips to overdue as soon as the due day starts. Bills due today will already render the error state and alert icon.

⏰ Proposed fix
         {`#each` payments as payment}
           {`@const` paymentForm = togglePayment.for(payment.id)}
+          {`@const` isPastDue =
+            payment.paidAt === null &&
+            payment.forMonthD.toISOString().slice(0, 10) <
+              new Date().toISOString().slice(0, 10)}
           <form {...paymentForm}>
             <div
               class={[
                 'card',
                 {
                   'variant-outline-success': payment.paidAt !== null,
-                  'variant-outline-error':
-                    payment.paidAt === null && payment.forMonthD < new Date(),
+                  'variant-outline-error': isPastDue,
                 },
               ]}
               role="listitem"
             >
@@
-                    {:else if payment.paidAt === null && payment.forMonthD < new Date()}
+                    {:else if isPastDue}
                       <ClockAlertIcon class="text-error-500" size="1em" />
                     {/if}

Also applies to: 153-154

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte around lines 141 -
143, The comparison uses payment.forMonthD < new Date() which compares a
date-only value to a timestamp and makes bills due today appear overdue; update
the checks that set 'variant-outline-error' (the expressions using
payment.forMonthD < new Date() at the shown condition and the similar
occurrences at lines 153-154) to compare only the date portions (e.g., normalize
both sides to start-of-day or compare formatted yyyy-mm-dd / toDateString()) so
that payments due today are not treated as past due—apply the same normalization
in every place that currently does payment.forMonthD < new Date().

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/website/src/lib/remotes/payments.remote.ts (1)

335-338: ⚠️ Potential issue | 🟡 Minor

Type annotation sql<string> contradicts .mapWith() behavior.

The sql<string> declares the return as a string, but .mapWith(schema.payments.forMonthD) uses the column's decoder, which converts to Date (since forMonthD has mode: 'date'). The actual runtime type is Date, not string.

This mismatch can cause TypeScript confusion downstream.

🔧 Proposed fix
         month:
-          sql<string>`date_trunc('month', ${schema.payments.forMonthD})::timestamp at time zone 'UTC'`.mapWith(
+          sql<Date>`date_trunc('month', ${schema.payments.forMonthD})::timestamp at time zone 'UTC'`.mapWith(
             schema.payments.forMonthD,
           ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/lib/remotes/payments.remote.ts` around lines 335 - 338, The
sql<> generic for the "month" field is declared as sql<string> but
.mapWith(schema.payments.forMonthD) decodes to a Date; update the SQL type
annotation to match the decoder (e.g., use sql<Date> or remove the generic so
the mapper's Date type is used) for the month property to ensure the declared
return type aligns with .mapWith(schema.payments.forMonthD) and avoid downstream
TypeScript type mismatches.
🧹 Nitpick comments (3)
apps/website/src/routes/dashboard/payments/+page.svelte (2)

113-115: Unused progress placeholder markup.

These empty <div class="progress"> and <div class="progress-track"> elements appear to be leftover code, as the actual <Progress> component is rendered separately below (lines 117-121).

🧹 Proposed fix
   <p class="text-zinc-400 mt-4">Monthly Progress</p>

-  <div class="progress">
-    <div class="progress-track"></div>
-  </div>
-
   <Progress
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte around lines 113 -
115, Remove the leftover empty placeholder markup: delete the two divs with
class="progress" and class="progress-track" since the real component <Progress>
is rendered separately (see the existing <Progress> usage) and these empty
elements are unused; ensure no other logic references the "progress" or
"progress-track" elements before removing them.

90-99: Duplicate fetch of payment history months.

getPaymentHistoryMonths() is already called and stored in paymentHistoryMonths (line 30), but line 90 calls it again for the <select> options. Use the existing derived value instead.

♻️ Proposed fix
       <select name="currentMonth" class="select" bind:value={selectedMonth}>
-        <svelte:boundary>
-          {`#snippet` pending()}
-            <option disabled>Loading</option>
-          {/snippet}
-
-          {`#each` await getPaymentHistoryMonths() as month}
+          {`#each` paymentHistoryMonths as month}
             {`@const` dateObj = new Date(month.month)}
             <option value={month.month}>
               {dateObj.toLocaleDateString(undefined, {
                 month: 'long',
                 year: 'numeric',
                 timeZone: 'UTC',
               })}
             </option>
           {/each}
-        </svelte:boundary>
       </select>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte around lines 90 -
99, The select is re-fetching months by calling getPaymentHistoryMonths() again;
instead iterate over the already-fetched value paymentHistoryMonths in the
{`#each`} block (replace "{`#each` await getPaymentHistoryMonths() as month}" with
iterating over paymentHistoryMonths and keep the same option rendering, ensuring
you await or subscribe only if paymentHistoryMonths is a Promise/store), and
remove the duplicate getPaymentHistoryMonths() call.
apps/website/src/lib/remotes/payments.remote.ts (1)

68-75: Inconsistent extract field casing.

'month' (line 69) is lowercase with quotes while YEAR (line 73) is uppercase without quotes. Both work in PostgreSQL, but consistent style improves readability.

✨ Proposed fix
           eq(
-            sql`extract('month' from ${schema.payments.forMonthD})`,
+            sql`extract(month from ${schema.payments.forMonthD})`,
             isoMonth,
           ),
           eq(
-            sql`extract(YEAR from ${schema.payments.forMonthD})`,
+            sql`extract(year from ${schema.payments.forMonthD})`,
             isoYear,
           ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/lib/remotes/payments.remote.ts` around lines 68 - 75,
Summary: inconsistent casing/quoting in the SQL EXTRACT calls. Fix: make both
EXTRACT units use the same style — for example change the second call to use a
lowercase quoted unit like the first, i.e. use sql`extract('year' from
${schema.payments.forMonthD})` so both EXTRACTs use quoted lowercase; locate the
two occurrences around the eq/sql calls that reference schema.payments.forMonthD
and isoMonth/isoYear and update the uppercase YEAR to 'year' to match the
'month' style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/website/src/lib/remotes/dashboard.remote.ts`:
- Around line 36-42: The month/year filters are mixing server-local JS time
(today) with DB now(), causing inconsistent timezone boundaries; change to use
JS-derived isoMonth/isoYear consistently (as in payments.remote.ts) instead of
sql`extract('year' from now())`: compute isoMonth and isoYear from today and
replace the year-extract clause that references now() with an equality against
the JS isoYear, keeping the month clause using today.getMonth()+1 (or using
isoMonth) and the payment column reference schema.payments.forMonthD so both
comparisons use the same time source.

---

Duplicate comments:
In `@apps/website/src/lib/remotes/payments.remote.ts`:
- Around line 335-338: The sql<> generic for the "month" field is declared as
sql<string> but .mapWith(schema.payments.forMonthD) decodes to a Date; update
the SQL type annotation to match the decoder (e.g., use sql<Date> or remove the
generic so the mapper's Date type is used) for the month property to ensure the
declared return type aligns with .mapWith(schema.payments.forMonthD) and avoid
downstream TypeScript type mismatches.

---

Nitpick comments:
In `@apps/website/src/lib/remotes/payments.remote.ts`:
- Around line 68-75: Summary: inconsistent casing/quoting in the SQL EXTRACT
calls. Fix: make both EXTRACT units use the same style — for example change the
second call to use a lowercase quoted unit like the first, i.e. use
sql`extract('year' from ${schema.payments.forMonthD})` so both EXTRACTs use
quoted lowercase; locate the two occurrences around the eq/sql calls that
reference schema.payments.forMonthD and isoMonth/isoYear and update the
uppercase YEAR to 'year' to match the 'month' style.

In `@apps/website/src/routes/dashboard/payments/`+page.svelte:
- Around line 113-115: Remove the leftover empty placeholder markup: delete the
two divs with class="progress" and class="progress-track" since the real
component <Progress> is rendered separately (see the existing <Progress> usage)
and these empty elements are unused; ensure no other logic references the
"progress" or "progress-track" elements before removing them.
- Around line 90-99: The select is re-fetching months by calling
getPaymentHistoryMonths() again; instead iterate over the already-fetched value
paymentHistoryMonths in the {`#each`} block (replace "{`#each` await
getPaymentHistoryMonths() as month}" with iterating over paymentHistoryMonths
and keep the same option rendering, ensuring you await or subscribe only if
paymentHistoryMonths is a Promise/store), and remove the duplicate
getPaymentHistoryMonths() call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f9363d4-8eaa-4a7a-9e61-451ef3632092

📥 Commits

Reviewing files that changed from the base of the PR and between d09d07e and e2bd9f1.

📒 Files selected for processing (3)
  • apps/website/src/lib/remotes/dashboard.remote.ts
  • apps/website/src/lib/remotes/payments.remote.ts
  • apps/website/src/routes/dashboard/payments/+page.svelte

Comment on lines 36 to +42
sql`extract('month' from ${schema.payments.forMonthD})`,
today.getMonth() + 1,
),
eq(
sql`extract('year' from ${schema.payments.forMonthD})`,
sql`extract('year' from now())`,
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Month/year constraints use inconsistent time sources.

The month comparison (line 37) uses the JavaScript today object (server-local timezone), while the year comparison (lines 40-41) uses the database's now() (session timezone). If these timezones diverge—especially around midnight—month and year could reference different calendar days, causing payments to be incorrectly included or excluded.

For consistency, either use the JS-derived values for both (as done in payments.remote.ts with isoYear/isoMonth) or use now() for both.

🛠️ Proposed fix using consistent JS-derived values
     .leftJoin(
       schema.payments,
       and(
         eq(
           sql`extract('month' from ${schema.payments.forMonthD})`,
           today.getMonth() + 1,
         ),
         eq(
           sql`extract('year' from ${schema.payments.forMonthD})`,
-          sql`extract('year' from now())`,
+          today.getFullYear(),
         ),
         eq(schema.payments.billId, schema.bills.id),
       ),
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/website/src/lib/remotes/dashboard.remote.ts` around lines 36 - 42, The
month/year filters are mixing server-local JS time (today) with DB now(),
causing inconsistent timezone boundaries; change to use JS-derived
isoMonth/isoYear consistently (as in payments.remote.ts) instead of
sql`extract('year' from now())`: compute isoMonth and isoYear from today and
replace the year-extract clause that references now() with an equality against
the JS isoYear, keeping the month clause using today.getMonth()+1 (or using
isoMonth) and the payment column reference schema.payments.forMonthD so both
comparisons use the same time source.

@jhechtf jhechtf marked this pull request as ready for review April 4, 2026 22:41
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 3 file(s) based on 6 unresolved review comments.

A stacked PR containing fixes has been created.

  • Stacked PR: #141
  • Files modified:
  • apps/website/src/lib/remotes/payments.remote.ts
  • apps/website/src/lib/server/db/client.ts
  • apps/website/src/routes/dashboard/payments/+page.svelte

Time taken: 4m 33s

Copy link
Copy Markdown
Contributor Author

jhechtf commented Apr 5, 2026

Merge activity

  • Apr 5, 7:58 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 5, 8:04 AM UTC: Graphite rebased this pull request as part of a merge.
  • Apr 5, 8:05 AM UTC: @jhechtf merged this pull request with Graphite.

@jhechtf jhechtf changed the base branch from feat/date-utils to graphite-base/140 April 5, 2026 08:01
@jhechtf jhechtf changed the base branch from graphite-base/140 to main April 5, 2026 08:02
@jhechtf jhechtf force-pushed the fix/payments-page branch from 0912161 to d97bf99 Compare April 5, 2026 08:03
@jhechtf jhechtf merged commit 472562f into main Apr 5, 2026
5 of 7 checks passed
@jhechtf jhechtf deleted the fix/payments-page branch April 5, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant