Skip to content
Open
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
25 changes: 21 additions & 4 deletions src/components/Billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,16 @@ const Billing: React.FC = () => {
<th className="pb-3 font-medium">Date</th>
<th className="pb-3 font-medium">Amount</th>
<th className="pb-3 font-medium">Status</th>
<th className="pb-3 font-medium text-right">Invoice</th>
</tr>
</thead>
<tbody className="divide-y divide-neutral-800">
{billingHistory.map(payment => (
<tr key={payment.id} className="text-sm">
<td className="py-4 text-white">
<td className="py-4 font-medium text-white">
{new Date(payment.date).toLocaleDateString()}
</td>
<td className="py-4 text-white">
<td className="py-4">
{(payment.amount / 100).toLocaleString(
'en-US',
{
Expand All @@ -327,6 +328,22 @@ const Billing: React.FC = () => {
payment.status.slice(1)}
</span>
</td>
<td className="py-4 text-right">
{payment.invoiceUrl ? (
<a
Comment on lines +331 to +333
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

With the new invoiceUrl field, the billingHistory items are implicitly expected to have a specific shape, but the state is typed as any[]. This makes it easy to ship runtime errors (e.g., missing currency, status, invoiceUrl). Define a BillingHistoryItem type/interface (with invoiceUrl?: string) and use it for billingHistory and the map callback.

Copilot uses AI. Check for mistakes.
href={payment.invoiceUrl}
target="_blank"
Comment on lines +332 to +335
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

payment.invoiceUrl is rendered directly into an href. If this value can ever be influenced by API data, a javascript:/non-http(s) URL could become a clickable XSS vector. Consider validating/normalizing the URL before rendering (e.g., only allow http/https and otherwise treat it as unavailable).

Copilot uses AI. Check for mistakes.
rel="noopener noreferrer"
className="text-primary-400 hover:text-primary-300 transition-colors inline-flex items-center gap-1 text-sm"
>
Download
</a>
) : (
<span className="text-neutral-600 cursor-not-allowed inline-flex items-center gap-1 text-sm" title="Invoice not available">
Unavailable
Comment on lines +342 to +343
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

CI is currently blocked by formatting issues.

Pipeline already reports Prettier failure; please run Prettier on this file before merge (the JSX wrapping around these changed lines is a likely contributor).

Also applies to: 379-380

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

In `@src/components/Billing.tsx` around lines 342 - 343, Prettier formatting is
failing in the Billing component around the JSX that renders the "Unavailable"
span; run Prettier on src/components/Billing.tsx to reformat the JSX (the span
with className "text-neutral-600 cursor-not-allowed inline-flex items-center
gap-1 text-sm" and the surrounding JSX blocks around lines where "Unavailable"
appears, and the similar JSX near lines 379-380) so the file conforms to project
Prettier rules and the CI pipeline will pass.

</span>
)}
Comment on lines +332 to +345
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

Validate payment.invoiceUrl before rendering it as a link.

On Line 334, href is populated directly from API data. If a non-HTTPS or malformed URL is returned, this can create a client-side security risk. Gate the link behind strict URL validation and show “Unavailable” when invalid.

🔒 Suggested fix
+  const getSafeInvoiceUrl = (rawUrl?: string) => {
+    if (!rawUrl) return null;
+    try {
+      const parsed = new URL(rawUrl);
+      return parsed.protocol === 'https:' ? parsed.toString() : null;
+    } catch {
+      return null;
+    }
+  };
...
-                                {payment.invoiceUrl ? (
+                                {getSafeInvoiceUrl(payment.invoiceUrl) ? (
                                   <a
-                                    href={payment.invoiceUrl}
+                                    href={getSafeInvoiceUrl(payment.invoiceUrl)!}
                                     target="_blank"
                                     rel="noopener noreferrer"
                                     className="text-primary-400 hover:text-primary-300 transition-colors inline-flex items-center gap-1 text-sm"
                                   >
                                     Download
                                   </a>
                                 ) : (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Billing.tsx` around lines 332 - 345, Validate
payment.invoiceUrl before rendering the anchor: implement or call a helper
(e.g., isValidInvoiceUrl) that attempts to construct a URL object from
payment.invoiceUrl and ensures url.protocol === 'https:' (reject malformed or
non-HTTPS URLs), then render the <a> only when that validation passes and fall
back to the "Unavailable" <span> otherwise; update the conditional around
payment.invoiceUrl in the Billing component to use that validation check.

</td>
</tr>
))}
</tbody>
Expand Down Expand Up @@ -359,8 +376,8 @@ const Billing: React.FC = () => {
? 'border-white bg-neutral-800/60 ring-1 ring-white shadow-[0_0_30px_rgba(255,255,255,0.05)]'
: 'border-white/10 hover:border-white/20 hover:bg-white/5',
plan.highlight &&
selectedPlan !== plan.id &&
'border-amber-500/30'
selectedPlan !== plan.id &&
'border-amber-500/30'
Comment on lines 378 to +380
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The amber border highlight is now applied to every non-selected plan because the plan.highlight && guard was removed. This makes all unselected plans look "highlighted" and decouples the border styling from the Most Popular badge logic below. Restore the plan.highlight condition (or otherwise tie the border class to the same highlight rule).

Copilot uses AI. Check for mistakes.
)}
>
{plan.highlight && (
Expand Down
Loading