Ultracite#27
Conversation
|
@haydenbleasel is attempting to deploy a commit to the Databuddy Team on Vercel. A member of the Team first needs to authorize it. |
## Walkthrough
This update is a comprehensive, project-wide code style and formatting refactor. It standardizes string quotes to single quotes, normalizes indentation and spacing, and reorganizes import statements for consistency across all application folders. No functional, logic, or control flow changes are introduced; all modifications are purely stylistic, affecting TypeScript, JavaScript, and JSX/TSX files, as well as configuration and test files.
## Changes
| File(s) / Group | Change Summary |
|-------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| .vscode/settings.json, apps/api/tsconfig.json, apps/basket/tsconfig.json | Editor and TypeScript config formatting and settings updates |
| apps/api/.gitignore, apps/api/package.json, apps/basket/package.json | Minor formatting: removed .eslintcache from .gitignore, added trailing newlines to package.json files |
| apps/api/biome.json | Deleted Biome configuration file |
| apps/api/src/agent/**/*, apps/api/src/query/**/*, apps/api/src/routes/**/*, apps/api/src/index.ts| Consistent formatting: single quotes, indentation, import reordering, trailing commas, code style normalization |
| apps/api/src/types/tables.ts, apps/api/src/query/types.ts, apps/api/src/query/constants.ts | Type and constant file formatting: single quotes, indentation, minor type annotation tweaks |
| apps/basket/src/**/* | Single quotes, indentation, import reordering, object and array formatting, code style consistency |
| apps/dashboard/app/**/* | Single quotes, import reordering, JSX/TSX multiline formatting, prop ordering, whitespace and style normalization |
| apps/api/src/agent/index.ts, apps/dashboard/app/(main)/billing/components/overview-tab.tsx, ... | Minor type annotation and export ordering updates |
| apps/api/src/query/builders/*.ts, apps/api/src/query/utils.ts | SQL builder and utility file formatting: string literal normalization, multiline arrays/objects, trailing commas |
## Estimated code review effort
🎯 2 (Simple) | ⏱️ ~8 minutes
## Poem
> Hoppity hop, a code-style sweep,
> Single quotes and tidy lines we keep.
> Indents aligned, imports in a row,
> The garden of code now neatly will grow.
> No logic disturbed, just a cleaner view—
> A rabbit’s delight: fresh code, brand new!
> 🐇✨Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 30
🔭 Outside diff range comments (11)
apps/dashboard/app/(main)/organizations/components/error-display.tsx (2)
27-31: Avoid leaking raw error messages to end-usersRendering
error.messageverbatim may disclose stack traces, internal paths or PII coming from upstream APIs. Gate this behind adevelopmentcheck (or a dedicated prop) and fall back to a generic message in production.-{error && ( - <pre className="max-h-[150px] overflow-auto rounded bg-muted p-3 font-mono text-xs"> - {error.message} - </pre> -)} +{error && + process.env.NODE_ENV === 'development' && ( + <pre className="max-h-[150px] overflow-auto rounded bg-muted p-3 font-mono text-xs"> + {error.message} + </pre> + )}
32-36: Add accessibility attributes to the retry button & iconThe button is purely icon-driven; screen-reader users need an
aria-label. Likewise, mark the SVG as decorative.-<Button onClick={onRetry} size="sm" variant="outline"> - <ArrowClockwise className="mr-2 h-4 w-4" /> +<Button + onClick={onRetry} + size="sm" + variant="outline" + aria-label="Retry loading organizations" +> + <ArrowClockwise className="mr-2 h-4 w-4" aria-hidden="true" /> Retry </Button>apps/dashboard/app/(main)/revenue/_components/onboarding/configuration-summary.tsx (1)
47-53: Mask the token to avoid accidental exposureDisplaying the full
webhookTokenin the UI risks leaking secrets (e.g., during screen-sharing or screenshots). Show only a short prefix/suffix instead.- <p className="font-mono">{webhookToken || 'Not configured'}</p> + <p className="font-mono"> + {webhookToken + ? `${webhookToken.slice(0, 6)}…${webhookToken.slice(-4)}` + : 'Not configured'} + </p>apps/dashboard/app/(main)/revenue/_components/quick-settings-modal.tsx (1)
81-88: Avoid premature success toast – wait foronSaveto resolve
onSaveis invoked and the success toast is fired immediately afterwards.
IfonSaveis async and rejects (e.g. network/API error), the UI will still announce success.- onSave({ - webhookSecret: localWebhookSecret, - isLiveMode: localIsLiveMode, - }); - setHasUnsavedChanges(false); - toast.success('Settings saved successfully'); + const result = await onSave({ + webhookSecret: localWebhookSecret, + isLiveMode: localIsLiveMode, + }); + if (result?.success) { + setHasUnsavedChanges(false); + toast.success('Settings saved successfully'); + }Alternatively, have
onSavereturn a promise and handle the toast in the calling component.apps/dashboard/app/(main)/revenue/_components/tabs/settings-tab.tsx (1)
169-182: Keystroke-level API calls – debounce or defer save
onChangedirectly callssetWebhookSecret, which ultimately triggerscreateOrUpdateMutation.mutate.
This fires a network request on every keystroke of the secret field:
- Unnecessary server load.
- Risk of rate-limiting / throttling.
- Possible partial secrets stored if user abandons typing.
Consider buffering the input locally and persisting only on explicit save / blur:
-<Input - … - onChange={(e) => setWebhookSecret(e.target.value)} - … -/> +const [draftSecret, setDraftSecret] = useState(''); +… +<Input + … + onChange={(e) => setDraftSecret(e.target.value)} + … +/> +// within handleSave or a dedicated onBlur handler +if (draftSecret.trim()) { + setWebhookSecret(draftSecret.trim()); +}This keeps the UX snappy while dramatically reducing backend chatter.
apps/dashboard/app/(main)/revenue/hooks/use-revenue-config.ts (1)
199-204: State setter name collides with updater – leads to implicit writes
setWebhookSecretimmediately callsupdateConfig, pushing every keystroke to the server.
Besides the performance issue flagged in the settings tab, the naming (setWebhookSecret) implies a local setter but has side-effects.Rename to
saveWebhookSecret(or similar) and expose a truly local setter for controlled inputs, or decouple persistence with debounce.apps/dashboard/app/(auth)/register/page.tsx (1)
454-468: Addrel="noopener noreferrer"to external links opened withtarget="_blank"Opening a new tab without these attributes enables the newly opened page to gain
window.openeraccess and potentially manipulate the parent (tab-nabbing).- <Link - className="font-medium text-primary hover:text-primary/80" - href="https://www.databuddy.cc/terms" - target="_blank" - > + <Link + className="font-medium text-primary hover:text-primary/80" + href="https://www.databuddy.cc/terms" + target="_blank" + rel="noopener noreferrer" + > @@ - <Link - className="font-medium text-primary hover:text-primary/80" - href="https://www.databuddy.cc/privacy" - target="_blank" - > + <Link + className="font-medium text-primary hover:text-primary/80" + href="https://www.databuddy.cc/privacy" + target="_blank" + rel="noopener noreferrer" + >apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx (1)
72-99:updateOrganizationshould be awaited to avoid race conditions
handleSaveis not declaredasync, soupdateOrganization(likely async) isn’t awaited. This means:
toast.successis fired before the mutation actually resolves.catchwill never trigger on request failures, as Promise rejections escape thetry/catch.setIsSaving(false)executes immediately, removing the loading state too early.-const handleSave = () => { +const handleSave = async () => { … - setIsSaving(true); - try { - updateOrganization({ + setIsSaving(true); + try { + await updateOrganization({ organizationId: organization.id, data: { name: name.trim(), slug: slug.trim(), }, }); toast.success('Organization updated successfully');This single change lets the loader, success toast and error handling reflect real network state.
apps/dashboard/app/(main)/organizations/[slug]/components/member-list.tsx (1)
60-69:Selectshould be controlled, not initialised withdefaultValue
After a role change the component receives a newmember.role, but the selector still shows the old value becausedefaultValueis only read once. Use the controlledvalueprop instead.- <Select - defaultValue={member.role} + <Select + value={member.role}This keeps the UI in sync with server state and avoids user confusion.
apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (1)
60-77: Await the asynchronous deletion call
deleteOrganizationis likelyasync. Withoutawait, rejection errors bypass the surroundingtry/catch, andfinallyclears the loading state too early.- deleteOrganization(organizationId); + await deleteOrganization(organizationId);apps/basket/src/utils/ip-geo.ts (1)
90-97: IPv6 validation rejects perfectly valid addresses
isValidIponly accepts fully-expanded IPv6 strings. Short-hand forms such as2001:db8:85a3::8a2e:370:7334(which you already test for) or::ffff:192.0.2.1will be rejected, causing unnecessary “geo-location disabled” fall-backs.A zero-maintenance fix is to delegate to Node’s built-in
net.isIP, which handles both IPv4 & IPv6 (compressed or not) and avoids maintaining brittle regexes:-import { createHash } from 'node:crypto'; +import { createHash } from 'node:crypto'; +import { isIP } from 'node:net'; ... -function isValidIp(ip: string): boolean { - if (!ip) return false; - - // Check for IPv4 format - const ipv4Regex = - /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - if (ipv4Regex.test(ip)) return true; - - // Check for IPv6 format (basic check) - const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; - if (ipv6Regex.test(ip)) return true; - - return false; +function isValidIp(ip: string): boolean { + return isIP(ip) !== 0; }This both simplifies the code and fixes the false-negative.
♻️ Duplicate comments (1)
apps/dashboard/app/(auth)/login/verification-needed/page.tsx (1)
71-78: Consistent cleanup pattern mirrors earlier comment
Same loading-state duplication noted in the magic-link page; applying thetry/finallypattern here will keep state handling consistent.
🧹 Nitpick comments (68)
apps/dashboard/app/(main)/organizations/components/onboarding-card.tsx (3)
24-41: Consider memoising or hoisting thefeaturesarray
featuresis recreated on every render.
While the cost is negligible here, extracting the constant outside the component or wrapping it inuseMemoprevents unnecessary allocations and helps the component remain referentially stable if it ever becomes more complex.-import { +import { BuildingsIcon, ChartLineIcon, ShieldIcon, UsersIcon, } from '@phosphor-icons/react'; +import { useMemo } from 'react'; ... - const features = [ + const features = useMemo( + () => [ { icon: UsersIcon, title: 'Team Collaboration', description: 'Invite team members and manage permissions' }, { icon: ChartLineIcon, title: 'Shared Analytics', description: 'Share insights and reports across your team' }, { icon: ShieldIcon, title: 'Role-Based Access', description: 'Control who can view and manage your data' }, - ]; + ], + [], + );
60-67: Addaria-hiddento decorative icons for better a11yThe SVGs are purely decorative; convey no additional information beyond the adjacent text.
Mark them as hidden from assistive technologies to reduce noise in the accessibility tree.-<feature.icon - className="h-5 w-5 text-primary" - size={20} - weight="duotone" -/> +<feature.icon + aria-hidden="true" + className="h-5 w-5 text-primary" + size={20} + weight="duotone" +/>
46-50: Same a11y tweak for the other icon instancesApply
aria-hidden="true"to the banner icon and the button icon as well for consistency.Also applies to: 78-80
apps/dashboard/app/(main)/layout.tsx (2)
11-12:await headers()is unnecessary noise
next/headersreturns aHeadersobject synchronously. Awaiting a non-Promise works but is misleading and may trigger ESLint warnings.- const session = await auth.api.getSession({ headers: await headers() }); + const session = await auth.api.getSession({ headers: headers() });
6-10: Consider typing the props instead of an inline object
Extracting an explicitMainLayoutPropsinterface improves readability and reuse.-export default async function MainLayout({ - children, -}: { - children: React.ReactNode; -}) { +interface MainLayoutProps { + children: React.ReactNode; +} + +export default async function MainLayout({ children }: MainLayoutProps) {apps/dashboard/app/(auth)/login/magic/page.tsx (1)
24-44: GuaranteesetIsLoading(false)in all paths
setIsLoading(false)is currently duplicated inside each success/error callback and thecatchblock. IfsignIn.magicLinkthrows before invokingfetchOptions,isLoadingmay remaintrue. Wrap the call intry/finallyto ensure consistent cleanup.- try { - await signIn.magicLink({ + try { + await signIn.magicLink({ ... - }); - } catch (error) { - setIsLoading(false); - toast.error('Failed to send magic link. Please try again.'); + }); + } catch { + toast.error('Failed to send magic link. Please try again.'); + } finally { + setIsLoading(false); }apps/dashboard/app/(auth)/layout.tsx (1)
28-36: Use<a>+target="_blank"for external links instead ofnext/link
next/linkis optimised for internal routing; when thehrefis an absolute URL (https://www.databuddy.cc) it falls back to a normal anchor, but it still omits the usual security attributes. Prefer:<a href="https://www.databuddy.cc" target="_blank" rel="noopener noreferrer" className="relative z-10" >This avoids pre-fetch overhead and prevents reverse-tab-nabbing.
apps/dashboard/app/(auth)/login/forgot/page.tsx (1)
24-40: DuplicatesetIsLoading(false)invocations
setIsLoading(false)is executed:
- Inside
fetchOptions.onSuccess- Inside
fetchOptions.onError- In the
catchblockThe outer
try…catchwill also run after the promise resolves, so state is toggled multiple times. Not harmful, but unnecessary noise in React DevTools. Consider removing the inner calls and rely onfinally:try { await authClient.forgotPassword({ … }); - // onSuccess: setIsLoading(false) <-- remove } catch (error) { toast.error('An error occurred. Please try again later.'); } finally { setIsLoading(false); }apps/dashboard/app/(auth)/login/page.tsx (1)
195-199: Minor UX: “Last used” badge overlaps input border on narrow screensThe absolutely-positioned badge might overlap the input’s right padding on very small widths. Consider adding a right-margin to the input (e.g.
pr-16) or moving the badge below the field.apps/dashboard/app/(main)/invitations/[id]/page.tsx (2)
65-74: String-matching on error messages is brittle
invitationError?.message?.includes('expired')etc. tightly couples logic to server wording and is case-sensitive.
Prefer error codes/enums returned by the API, or at least normalise the message:- invitationError?.message?.includes('expired') || - invitationError?.message?.includes('not found') + /expired|not found/i.test(invitationError?.message ?? '')This avoids false negatives if the backend message changes case.
134-142:formatExpiryDatecan acceptDateto remove caller conversions
formatExpiryDatecurrently expects a string, forcing every caller to convert. ReceiveDate | stringand normalise inside:-const formatExpiryDate = (expiresAt: string) => { - const date = new Date(expiresAt); +const formatExpiryDate = (expiresAt: string | Date) => { + const date = new Date(expiresAt);Removes repetitive
.toString()/string literals and reduces error surface.apps/dashboard/app/(main)/sandbox/api-testing/page.tsx (1)
21-23: Minor UX improvement idea
The default headers string now contains explicit new-lines (\n). In the rendered<Textarea>this produces the desired formatting, but when the string is round-tripped throughsetHeadersthe\nare preserved literally.
Consider initialising with a multi-line template literal to avoid the escape sequences inside the UI:-const [headers, setHeaders] = useState( - '{\n "Content-Type": "application/json"\n}' -); +const [headers, setHeaders] = useState(`{ + "Content-Type": "application/json" +}`);apps/dashboard/app/(main)/revenue/_components/onboarding/onboarding-flow.tsx (1)
33-38: Avoid recreating thestepsarray on every renderThe constant is stable; wrap it in
useMemo(or move it outside the component) to prevent needless allocations and to keep referential equality for child props.- const steps: OnboardingStep[] = [ - 'overview', - 'webhook', - 'testing', - 'complete', - ]; + const steps: OnboardingStep[] = useMemo( + () => ['overview', 'webhook', 'testing', 'complete'], + [] + );Remember to
import { useMemo } from 'react';.apps/dashboard/app/(main)/revenue/page.tsx (1)
114-117: Minor wording nit“one-time setup that works for all your sites” – consider dropping “one-time” if future edits can occur, or clarify. Purely copy related; ignore if intentional.
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx (2)
75-81:Inputcould use semantic typeIf the component supports the
typeprop, passtype="url"to enable native validation / copy helpers in some browsers.
82-95: Copy feedback race condition
copiedis supplied by the parent, but rapid successive clicks may flicker icon states. Consider debouncing or locally setting a brief success state to avoid UX jitter.apps/dashboard/app/(main)/revenue/_components/onboarding/steps/testing-step.tsx (1)
37-39: Label mismatchThe label
'Test command'is generic; consider something more specific (e.g.,'Stripe CLI command') to aid analytics or toast messages.apps/dashboard/app/(main)/revenue/_components/tabs/settings-tab.tsx (1)
141-149: Reuse existing clipboard helper to unify error handling
handleCopyre-implements clipboard logic already available ascopyToClipboardin other components / utils.
Import and reuse it to avoid divergence in behaviour & error reporting.apps/dashboard/app/(main)/revenue/hooks/use-revenue-config.ts (1)
175-180: Clipboard helper duplicates logic across componentsSame copy-to-clipboard implementation exists in multiple places. Consolidate into a shared util (e.g.
lib/utils/clipboard.ts) so error handling & toast text remain consistent.apps/dashboard/app/(auth)/register/page.tsx (2)
81-88: Avoid storing auth tokens inlocalStorage– switch to Http-Only cookiesPersisting sensitive session data in
localStorageexposes it to JavaScript (and thus XSS). Consider returning the token in aSet-Cookieheader with theHttpOnly,Secure, andSameSiteflags and let the browser manage it automatically.
This keeps the credential out of the JS context and eliminates manual reads/writes in both success callbacks.Also applies to: 135-141
432-435: Cast inonCheckedChangehides potential API-shape issues
onCheckedChangefrom Radix UI returnsboolean | "indeterminate".
Blind-casting tobooleanmay silently coerce"indeterminate"totrue, defeating the honeypot / T&C logic. Safeguard the value instead:-onCheckedChange={(checked) => setIsHoneypot(checked as boolean)} +onCheckedChange={(checked) => + setIsHoneypot(checked === true) +}Repeat the same pattern for
setAcceptTerms.Also applies to: 445-447
apps/dashboard/app/(main)/billing/page.tsx (1)
82-91: Addaria-hiddento decorative icons inside tab triggers.
<tab.icon />is purely visual; screen-readers will read the SVG title (often the filename), producing redundant output after the visible<span>label. Mark the icon as hidden to assistive tech:-<tab.icon className="mr-2 h-4 w-4" /> +<tab.icon aria-hidden="true" className="mr-2 h-4 w-4" />apps/dashboard/app/(main)/organizations/[slug]/components/teams-tab.tsx (1)
20-24: Silence duplicate screen-reader output from inline SVGs.Like the billing tabs, these inline
UsersIcon/BuildingsIconinstances are decorative and immediately followed by descriptive text. Addaria-hidden="true"(orrole="presentation") to prevent redundant announcements.-<UsersIcon className="h-8 w-8 text-primary" size={32} weight="duotone" /> +<UsersIcon + aria-hidden="true" + className="h-8 w-8 text-primary" + size={32} + weight="duotone" +/> -<UsersIcon className="h-3 w-3 text-success" size={12} /> +<UsersIcon aria-hidden="true" className="h-3 w-3 text-success" size={12} />apps/dashboard/app/(main)/organizations/[slug]/components/team-view.tsx (2)
21-29: Prefer precise typing for theiconprop
icon: anyloses all compile-time guarantees. Narrow the type toComponentType<IconProps>(or whatever@phosphor-icons/reactexports) so that only valid icon components are accepted and the JSX attributes are type-checked.-}: { - icon: any; - label: string; - value: number; -}) => ( +}: { + icon: React.ComponentType<React.ComponentProps<typeof UsersIcon>>; + label: string; + value: number; +}) => (
94-115: Memoise derived collections to avoid needless re-renders
activeInvitationsandtotalMembersare recomputed on every render.
Wrap them inuseMemoto prevent unnecessary work and to avoid triggering downstream memoised components when the source data hasn’t changed.-const activeInvitations = - invitations?.filter((inv) => inv.status === 'pending') || []; -const totalMembers = (members?.length || 0) + activeInvitations.length; +const activeInvitations = useMemo( + () => invitations?.filter((inv) => inv.status === 'pending') || [], + [invitations], +); +const totalMembers = useMemo( + () => (members?.length || 0) + activeInvitations.length, + [members, activeInvitations], +);apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx (1)
65-68: Remove theanycast on role selectionThe state is already constrained to
'owner' | 'admin' | 'member'; cast is unnecessary and hides typos.-<Select - onValueChange={(value) => setInviteRole(value as any)} - value={inviteRole} -> +<Select + onValueChange={(value) => + setInviteRole(value as 'owner' | 'admin' | 'member') + } + value={inviteRole} +>apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx (1)
138-151: Repeatedfiltercalls in render can be expensive
members?.filter(...)is executed three times. Cache the counts with a single pass oruseMemofor O(n) → O(1) during render.-<Badge ...> - {members?.filter((m) => m.role === 'admin').length || 0} -</Badge> +<Badge ...> + {adminCount} +</Badge>const { ownerCount, adminCount, memberCount } = useMemo(() => { const counts = { owner: 0, admin: 0, member: 0 }; members?.forEach((m) => counts[m.role]++); return { ownerCount: counts.owner, adminCount: counts.admin, memberCount: counts.member, }; }, [members]);apps/dashboard/app/(main)/organizations/[slug]/components/invitation-list.tsx (1)
106-108: Handle potential long email strings gracefullyLong email addresses might overflow the dialog title width. Consider truncating or moving the address into the description to preserve layout.
apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx (1)
40-43: Replaceanywith a typedOrganizationinterface
organization: anyforfeits type-safety in a core admin screen. Expose a minimalOrganizationtype (id, name, slug, logo, …) in@/typesand import it here.apps/dashboard/app/(main)/organizations/[slug]/components/organization-logo-uploader.tsx (1)
87-97: Add MIME-type / size guards before FileReaderOnly the
<input accept="…">attribute guards the picker UI. Users (or tests) can still drop any file onto the input.
Consider rejecting non-image files and oversized images up-front to avoid unnecessary FileReader work and potential DoS via huge files.apps/dashboard/app/(main)/organizations/[slug]/page.tsx (2)
26-31: Provide explicit prop types
SetActiveButtonreceives four props yet is typed asany. This leaks type-safety and makes refactors harder.-function SetActiveButton({ - onSetActive, - isSettingActive, - isCurrentlyActive, -}: any) { +interface SetActiveButtonProps { + onSetActive: () => void; + isSettingActive: boolean; + isCurrentlyActive: boolean; +} + +function SetActiveButton({ + onSetActive, + isSettingActive, + isCurrentlyActive, +}: SetActiveButtonProps) {
118-122: Avoid forcing full page reload on retry
window.location.reload()drops React state and incurs a cold start.
Prefer re-invoking theuseOrganizationsquery, or exposing itsrefetch()method, so only the needed data is reloaded and the UX remains smooth.apps/dashboard/app/(main)/organizations/[slug]/components/transfer-assets.tsx (1)
44-47:findscans both arrays every time
[...personalWebsites, ...organizationWebsites]allocates a new array on every click.
For larger lists consider a memoised Map keyed by id to achieve O(1) look-ups.apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (1)
88-97: Skeleton key should precede the className propMinor: The
key={i}prop is placed after several JSX props, which can hinder quick key scanning when debugging list rendering issues. Placingkeyfirst is a common convention.No functional change—just a readability nit.
apps/dashboard/app/(main)/billing/hooks/use-billing.ts (1)
22-36: Unused variableresult
handleUpgradestoresconst result = await attach(…)but never usesresult.
Either remove the assignment or use the value (e.g., for analytics) to avoid the eslint@typescript-eslint/no-unused-varswarning.apps/dashboard/app/(main)/billing/components/cancel-subscription-dialog.tsx (1)
31-34: Locale-agnostic date formatting may surprise users
new Date(currentPeriodEnd).toLocaleDateString()renders according to the server/browser locale, which can be inconsistent across users and environments. If the product requires a consistent format (e.g.MMM d, yyyy) consider specifying a locale or using a dedicated date-formatting util (date-fns, dayjs, Intl.DateTimeFormat).apps/dashboard/app/(main)/billing/components/pricing-tiers-tooltip.tsx (1)
81-85:key={tier.to}risks duplicate keys
tocan repeat across different plans ('inf'is identical for every last tier), which breaks React’s key uniqueness guarantee. Use the index or a composite key.- {tiers.map((tier) => ( + {tiers.map((tier, idx) => ( ... - key={tier.to} + key={`${tier.to}-${idx}`}apps/dashboard/app/(main)/billing/components/overview-tab.tsx (1)
144-151: Duplicate conditional renders identical text
isOverLimit ? 'Upgrade' : 'Upgrade'can be simplified.- {isOverLimit ? 'Upgrade' : 'Upgrade'} + Upgradeapps/dashboard/app/(main)/billing/components/history-tab.tsx (1)
245-249: Sorting mutates theinvoicesprop – clone before sorting
Array.prototype.sortsorts in place. Ifinvoicesis derived from props or a global store, this mutates shared state and can cause subtle bugs or unexpected re-renders. Clone before sorting.- {invoices - .sort((a: Invoice, b: Invoice) => b.created_at - a.created_at) + {[...invoices] + .sort((a: Invoice, b: Invoice) => b.created_at - a.created_at)apps/dashboard/app/(main)/organizations/components/organization-switcher.tsx (1)
29-30: Remove unusedisSettingActiveOrganizationvariable
isSettingActiveOrganizationis destructured but never used, which will trigger theno-unused-varsESLint / TypeScript rule and bloats the bundle.-const { setActiveOrganization, isSettingActiveOrganization } = useOrganizations(); +const { setActiveOrganization } = useOrganizations();apps/basket/src/lib/logger.ts (1)
5-9: Guard against missing ENV before instantiatingLogtail
tokenandendpointare cast tostring; if either env var is undefined you’ll end up sending the literal string"undefined"to Logtail, silently mis-configuring it.-const token = process.env.LOGTAIL_SOURCE_TOKEN as string; -const endpoint = process.env.LOGTAIL_ENDPOINT as string; +const token = process.env.LOGTAIL_SOURCE_TOKEN ?? ''; +const endpoint = process.env.LOGTAIL_ENDPOINT ?? ''; + +if (!token) { + console.warn('[logger] LOGTAIL_SOURCE_TOKEN is not set – logs will be dropped'); +}Optionally throw early in non-production builds.
apps/basket/src/index.ts (2)
40-41:porttype mismatch
process.env.PORTis a string; Elysia expects a number. Coerce explicitly to avoid accidental"3000"string ports on some runtimes.- port: process.env.PORT || 4000, + port: Number(process.env.PORT) || 4000,
11-17: Avoid losing original stack traceWrapping the original
errorin a newErrordiscards its stack. Pass the original object directly:-logger.error( - new Error(`${error instanceof Error ? error.name : 'Unknown'}: ${…}`) -); +logger.error(error);If you need custom formatting, attach metadata instead of re-wrapping.
apps/basket/src/hooks/auth.ts (2)
313-350: Duplicate caching utilities—consider consolidating
getWebsiteById/getOwnerIdand the later Cached variants implement identical logic with different cache keys. Keeping both increases maintenance overhead and risk of divergence.If two TTL/keys are required, expose the raw fetchers once and wrap with
cacheablein a single place:const fetchWebsite = (id: string) => db.query.websites.findFirst({ … }); export const getWebsiteById = cacheable(fetchWebsite, { … }); const fetchOwnerId = (w: Website) => _resolveOwnerId(w); export const getOwnerId = cacheable(fetchOwnerId, { … });
114-142:isValidOriginearly-exit may hide configuration errorsReturning
truewhenoriginHeaderis empty effectively disables origin checking for requests lacking the header (e.g., cURL or some older browsers). If the intent is to require the header, flip the logic or make it configurable.apps/api/src/types/tables.ts (1)
21-31: Reduce duplication by derivingTableFieldsMapfromAnalytics
TableFieldsMapmust be updated manually whenever a new analytics table is added.
A mapped-type keyed offAnalyticseliminates this maintenance risk:-export type TableFieldsMap = { - 'analytics.events': keyof AnalyticsEvent; - 'analytics.errors': keyof ErrorEvent; - 'analytics.web_vitals': keyof WebVitalsEvent; - 'analytics.stripe_payment_intents': keyof StripePaymentIntent; - 'analytics.stripe_charges': keyof StripeCharge; - 'analytics.stripe_refunds': keyof StripeRefund; - 'analytics.blocked_traffic': keyof BlockedTraffic; -}; +export type TableFieldsMap = { + [K in AnalyticsTable]: K extends 'analytics.events' + ? keyof AnalyticsEvent + : K extends 'analytics.errors' + ? keyof ErrorEvent + : K extends 'analytics.web_vitals' + ? keyof WebVitalsEvent + : K extends 'analytics.stripe_payment_intents' + ? keyof StripePaymentIntent + : K extends 'analytics.stripe_charges' + ? keyof StripeCharge + : K extends 'analytics.stripe_refunds' + ? keyof StripeRefund + : keyof BlockedTraffic; +};apps/basket/src/polyfills/compression.js (1)
10-24: Stream handles are never closed
zlibtransform streams hold native resources.
Consider exposingflush/closehelpers or forwardinghandle.close()on the returnedWritableStream’sclose()to avoid leaks in long-lived workers.apps/api/src/routes/health.ts (1)
37-61: Hard-coded version string
version: '1.0.0'will drift quickly. Pull the value frompackage.jsonor an environment variable to avoid manual updates.apps/api/src/middleware/rate-limit.ts (1)
12-15: Path check may miss/trpcroot
request.url.includes('/trpc/')skips/trpcbut not/trpcwithout a trailing slash.
Consider checkingstartsWith('/trpc')after stripping the origin for full coverage.apps/basket/src/utils/user-agent.ts (1)
84-92: Compile bot regexes once to cut per-request CPU
detectBotrebuilds everyRegExpon each call (bots.length≫ 1 000). Pre-compiling once at module load reduces GC pressure and latency.-import { bots } from '@databuddy/shared'; +import { bots as rawBots } from '@databuddy/shared'; + +const bots = rawBots.map((b) => ({ ...b, regex: new RegExp(b.regex, 'i') }));-const detectedBot = bots.find((bot) => new RegExp(bot.regex, 'i').test(ua)); +const detectedBot = bots.find((bot) => bot.regex.test(ua));apps/basket/src/utils/user-agent.test.ts (1)
57-70: Add a negative-parsing test for stronger coverage
parseUserAgent('garbage{')currently exercises the JSON-parser’s error branch inUAParser, but the suite never asserts on the"success: false"path.
A one-liner test would lock that behaviour and prevent silent regressions.apps/api/src/agent/utils/response-parser.ts (1)
13-17: Minor regexp hardening
replace(/```json\n?/g, '')andreplace(/```\n?/g, '')miss\r\n(Windows) line endings.
Consider\r?\n?to be platform-agnostic.- .replace(/```json\n?/g, '') - .replace(/```\n?/g, ''); + .replace(/```json\r?\n?/g, '') + .replace(/```\r?\n?/g, '');apps/api/src/query/screen-resolution-to-device-type.ts (1)
38-44: Landscape mobiles mis-classified asunknownA phone in landscape (e.g.
812x375, aspect ≈ 2.16) fails themobilecheck (aspect < 1.1) and thetabletcheck (width <= 800), ending up asunknown.
Unless intentional, broaden the mobile guard to include wider aspects when width ≤ 800.- if (width <= 800 && aspect < 1.1) return 'mobile'; + if (width <= 800) return 'mobile';apps/basket/src/hooks/auth.test.ts (1)
13-18: Reset mocked logger between tests
vi.mockreplaceslogger.warn/logger.error, but the mock state is never cleared. Residual call-counts can cause false positives in later assertions. Add a globalafterEach(vi.resetAllMocks)(or scoped reset) to avoid bleed-through.apps/basket/src/routes/stripe.ts (1)
68-73: Trim forwarded-for header to prevent leading/trailing spaces
split(',')[0]may still include a preceding space. Add.trim()to avoid bogus IPs.- request.headers.get('x-forwarded-for')?.split(',')[0] || + request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||apps/api/src/query/index.ts (2)
1-1: Use a single import path for Zod across the repoOther modules (e.g.,
apps/basket/src/utils/event-schema.ts) import Zod via'zod/v4', whereas this file uses the root package name. Mixing entry-points risks bundlers treating them as two distinct copies, breakinginstanceof ZodErrorchecks and increasing bundle size.-import { z } from 'zod'; +import { z } from 'zod/v4';(or switch every other file to
'zod', but pick one and standardise)
32-57: Eliminate duplication betweenexecuteQueryandcompileQueryBoth functions parse the request, look up the config, construct a
SimpleQueryBuilder, and only differ in the final method invoked (execute()vscompile()). Consolidating that common path into a small helper keeps the public API intact while shrinking maintenance surface.-const build = ( - request: QueryRequest, - websiteDomain?: string | null, - timezone?: string -) => { - const validated = QuerySchema.parse(request); - const config = QueryBuilders[validated.type]; - if (!config) throw new Error(`Unknown query type: ${validated.type}`); - return new SimpleQueryBuilder( - config, - { ...validated, timezone: timezone ?? validated.timezone }, - websiteDomain - ); -}; - -export const executeQuery = async ( +const build = ( request: QueryRequest, websiteDomain?: string | null, timezone?: string -) => { - const builder = build(request, websiteDomain, timezone); - return await builder.execute(); -}; +) => new SimpleQueryBuilder( + QueryBuilders[QuerySchema.parse(request).type], + { ...request, timezone }, + websiteDomain + ); + +export const executeQuery = async (...args: Parameters<typeof build>) => + (await build(...args)).execute();apps/api/src/agent/utils/stream-utils.ts (1)
8-18: Reuse a singleTextEncoderinstanceCreating a new
TextEncoderfor every chunk is minor but avoidable allocation. Hoisting one encoder outside the loop (or even outsidestart) is simpler and slightly faster.- const data = `data: ${JSON.stringify(update)}\n\n`; - controller.enqueue(new TextEncoder().encode(data)); + const data = `data: ${JSON.stringify(update)}\n\n`; + controller.enqueue(encoder.encode(data));const encoder = new TextEncoder();apps/api/src/agent/processor.ts (1)
35-40: Avoid logging raw user prompts in production
console.infocurrently emits the full user message and website identifiers. Even though this PR is mainly cosmetic, consider redacting or using structured logging with a log level that can be suppressed in prod to minimise PII exposure.-console.info('✅ [Assistant Processor] Input validated', { - message: request.message, +console.info('✅ [Assistant Processor] Input validated', { + message: process.env.NODE_ENV === 'development' ? request.message : '<redacted>',apps/basket/src/routes/basket.ts (2)
562-585: Prefer using the project logger overconsole.errorfor consistency
console.errorbypasses the structured logger and makes it harder to correlate messages in production logs.- console.error( - 'Blocked event schema errors:', - parseResult.error.issues, - 'Payload:', - body - ); + logger.warn('Blocked event schema errors', { + issues: parseResult.error.issues, + payload: body, + });
689-713: Duplicate diagnostic code – consider extracting helperThe “invalid_schema” handling block is repeated three times (track, error, web_vitals). Extracting a small helper would shrink the route body and lower the chance of divergence.
apps/api/src/agent/prompts/agent.ts (1)
128-142: Minor: potential HTML-escaping issue in history injectionUser/assistant messages are injected directly into an XML-like tag. If a message contains
</message>(unlikely but possible) it will break the prompt structure. Considerescape-ing angle brackets.apps/api/src/query/simple-builder.ts (1)
70-74: Tiny optimisation
dateStr.split('.')is called but only index 0 is used.dateStr.split('T')[0]would be clearer and faster.apps/api/src/query/utils.ts (2)
64-81: Performance: avoid repeatedsplitin domain matchingInside
getReferrerByDomain, every sub-domain path runsparts.slice(i).join('.')on each loop. Pre-computeparts.lengthand reuse or iterate with substring maths to cut allocations, especially on large datasets.
212-243: Percentage recomputation can exceed 100%After lumping resolutions into device types you simply sum previous percentages then recalc based on
pageviews, which may diverge from the original denominator used by upstream SQL. Consider dropping the carry-over sum and always recompute frompageviews/totalPageviewsto guarantee totals ≈ 100%.apps/api/src/routes/query.ts (3)
82-100: Factor out duplicated timezone-resolution logicThe logic that derives the final
timezonevalue appears twice verbatim. Consider extracting it into a small helper (e.g.,resolveTimezone(request, url, pref?)) to:
- Remove ~40 duplicate lines
- Guarantee the two branches never diverge subtly in the future
- Make the derive block far easier to scan
This is a low-risk, readability-first refactor.
Also applies to: 124-142
255-260: Avoid redundant DB look-ups for website domains
executeDynamicQuerycallsgetWebsiteDomaineven though the upstream.derive()has already fetched thewebsiteobject (for non-public sites) and placed it on the context.Passing
website?.domain(when available) intoexecuteDynamicQuerywould:
- Save one round-trip per query
- Reduce pressure on the
cacheablelayer- Keep public-website behaviour unchanged
Consider amending the call site and function signature to accept an optional
websiteDomainparameter.
261-264: Granularity→time-unit mapping is incomplete
getTimeUnit()collapses everything that isn’t'hourly' | 'hour'to'day'. If front-end code ever sends'daily'you get'day', which is fine, but'minute','week', etc. silently become'day', potentially generating bloated result sets.Either:
- Extend the mapping to cover every
timeUnityour builders support, or- Throw early when an unsupported granularity is provided.
Fail-fast beats silent downgrades.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (106)
.vscode/settings.json(1 hunks)apps/api/.gitignore(0 hunks)apps/api/biome.json(0 hunks)apps/api/package.json(1 hunks)apps/api/src/agent/handlers/chart-handler.ts(1 hunks)apps/api/src/agent/handlers/metric-handler.ts(1 hunks)apps/api/src/agent/index.ts(1 hunks)apps/api/src/agent/processor.ts(1 hunks)apps/api/src/agent/prompts/agent.ts(3 hunks)apps/api/src/agent/utils/ai-client.ts(1 hunks)apps/api/src/agent/utils/query-executor.ts(1 hunks)apps/api/src/agent/utils/response-parser.ts(1 hunks)apps/api/src/agent/utils/sql-validator.ts(1 hunks)apps/api/src/agent/utils/stream-utils.ts(1 hunks)apps/api/src/index.ts(3 hunks)apps/api/src/middleware/rate-limit.ts(1 hunks)apps/api/src/query/builders/custom-events.ts(1 hunks)apps/api/src/query/builders/devices.ts(1 hunks)apps/api/src/query/builders/errors.ts(1 hunks)apps/api/src/query/builders/geo.ts(1 hunks)apps/api/src/query/builders/index.ts(1 hunks)apps/api/src/query/builders/pages.ts(3 hunks)apps/api/src/query/builders/performance.ts(1 hunks)apps/api/src/query/builders/profiles.ts(7 hunks)apps/api/src/query/builders/sessions.ts(6 hunks)apps/api/src/query/builders/summary.ts(6 hunks)apps/api/src/query/builders/traffic.ts(1 hunks)apps/api/src/query/constants.ts(1 hunks)apps/api/src/query/index.ts(1 hunks)apps/api/src/query/screen-resolution-to-device-type.ts(1 hunks)apps/api/src/query/simple-builder.ts(1 hunks)apps/api/src/query/types.ts(1 hunks)apps/api/src/query/utils.ts(1 hunks)apps/api/src/routes/assistant.ts(1 hunks)apps/api/src/routes/health.ts(1 hunks)apps/api/src/routes/query.ts(1 hunks)apps/api/src/types/tables.ts(1 hunks)apps/api/tsconfig.json(0 hunks)apps/basket/package.json(1 hunks)apps/basket/src/hooks/auth.test.ts(1 hunks)apps/basket/src/hooks/auth.ts(7 hunks)apps/basket/src/index.ts(1 hunks)apps/basket/src/lib/logger.ts(2 hunks)apps/basket/src/polyfills/compression.js(1 hunks)apps/basket/src/routes/basket.ts(1 hunks)apps/basket/src/routes/stripe.ts(2 hunks)apps/basket/src/types/payments.ts(1 hunks)apps/basket/src/types/track.ts(1 hunks)apps/basket/src/utils/event-schema.ts(1 hunks)apps/basket/src/utils/ip-geo.test.ts(1 hunks)apps/basket/src/utils/ip-geo.ts(8 hunks)apps/basket/src/utils/user-agent.test.ts(1 hunks)apps/basket/src/utils/user-agent.ts(1 hunks)apps/basket/src/utils/validation.ts(13 hunks)apps/basket/tsconfig.json(1 hunks)apps/dashboard/app/(auth)/layout.tsx(2 hunks)apps/dashboard/app/(auth)/login/forgot/page.tsx(3 hunks)apps/dashboard/app/(auth)/login/magic-sent/page.tsx(3 hunks)apps/dashboard/app/(auth)/login/magic/page.tsx(3 hunks)apps/dashboard/app/(auth)/login/page.tsx(12 hunks)apps/dashboard/app/(auth)/login/verification-needed/page.tsx(3 hunks)apps/dashboard/app/(auth)/register/page.tsx(21 hunks)apps/dashboard/app/(main)/billing/components/cancel-subscription-dialog.tsx(1 hunks)apps/dashboard/app/(main)/billing/components/history-tab.tsx(1 hunks)apps/dashboard/app/(main)/billing/components/no-payment-method-dialog.tsx(1 hunks)apps/dashboard/app/(main)/billing/components/overview-tab.tsx(10 hunks)apps/dashboard/app/(main)/billing/components/plans-tab.tsx(1 hunks)apps/dashboard/app/(main)/billing/components/pricing-tiers-tooltip.tsx(1 hunks)apps/dashboard/app/(main)/billing/data/billing-data.ts(5 hunks)apps/dashboard/app/(main)/billing/hooks/use-billing.ts(1 hunks)apps/dashboard/app/(main)/billing/page.tsx(5 hunks)apps/dashboard/app/(main)/home/page.tsx(1 hunks)apps/dashboard/app/(main)/invitations/[id]/page.tsx(21 hunks)apps/dashboard/app/(main)/layout.tsx(1 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/invitation-list.tsx(6 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx(4 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/member-list.tsx(7 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/organization-logo-uploader.tsx(9 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/organization-page-skeleton.tsx(1 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx(9 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/settings-tab.tsx(12 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/team-view.tsx(2 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/teams-tab.tsx(3 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/transfer-assets.tsx(7 hunks)apps/dashboard/app/(main)/organizations/[slug]/components/website-selector.tsx(3 hunks)apps/dashboard/app/(main)/organizations/[slug]/page.tsx(12 hunks)apps/dashboard/app/(main)/organizations/components/error-display.tsx(2 hunks)apps/dashboard/app/(main)/organizations/components/onboarding-card.tsx(1 hunks)apps/dashboard/app/(main)/organizations/components/organization-switcher.tsx(1 hunks)apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx(10 hunks)apps/dashboard/app/(main)/organizations/components/stats-skeleton.tsx(1 hunks)apps/dashboard/app/(main)/organizations/page.tsx(9 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/configuration-summary.tsx(3 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/onboarding-flow.tsx(2 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/step-indicator.tsx(2 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/complete-step.tsx(2 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/overview-step.tsx(2 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/testing-step.tsx(2 hunks)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx(7 hunks)apps/dashboard/app/(main)/revenue/_components/quick-settings-modal.tsx(10 hunks)apps/dashboard/app/(main)/revenue/_components/tabs/overview-tab.tsx(3 hunks)apps/dashboard/app/(main)/revenue/_components/tabs/settings-tab.tsx(13 hunks)apps/dashboard/app/(main)/revenue/hooks/use-revenue-config.ts(6 hunks)apps/dashboard/app/(main)/revenue/page.tsx(9 hunks)apps/dashboard/app/(main)/revenue/utils/types.ts(1 hunks)apps/dashboard/app/(main)/sandbox/api-testing/page.tsx(10 hunks)
💤 Files with no reviewable changes (3)
- apps/api/.gitignore
- apps/api/tsconfig.json
- apps/api/biome.json
🧰 Additional context used
🧬 Code Graph Analysis (32)
apps/dashboard/app/(main)/billing/components/plans-tab.tsx (1)
apps/dashboard/components/autumn/pricing-table.tsx (1)
PricingTable(13-183)
apps/dashboard/app/(main)/revenue/_components/onboarding/onboarding-flow.tsx (5)
apps/dashboard/app/(main)/revenue/_components/onboarding/step-indicator.tsx (1)
StepIndicator(11-36)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/overview-step.tsx (1)
OverviewStep(10-57)apps/database/lib/utils.ts (1)
copyToClipboard(57-65)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/testing-step.tsx (1)
TestingStep(12-77)apps/dashboard/app/(main)/revenue/_components/onboarding/steps/complete-step.tsx (1)
CompleteStep(10-43)
apps/dashboard/app/(main)/revenue/_components/quick-settings-modal.tsx (1)
apps/database/lib/utils.ts (1)
copyToClipboard(57-65)
apps/api/src/query/builders/custom-events.ts (1)
apps/api/src/query/types.ts (1)
SimpleQueryConfig(30-56)
apps/basket/src/utils/user-agent.test.ts (1)
apps/basket/src/utils/user-agent.ts (2)
detectBot(73-119)parseUserAgent(25-71)
apps/api/src/query/builders/profiles.ts (3)
apps/api/src/query/types.ts (1)
SimpleQueryConfig(30-56)apps/api/src/types/tables.ts (1)
Analytics(11-19)packages/db/src/clickhouse/query_builder.ts (2)
limit(322-327)offset(332-337)
apps/api/src/agent/utils/stream-utils.ts (2)
apps/api/src/agent/index.ts (3)
createStreamingResponse(18-18)StreamingUpdate(16-16)generateThinkingSteps(19-19)apps/dashboard/error.tsx (1)
Error(8-44)
apps/basket/src/utils/user-agent.ts (1)
packages/shared/src/lists/bots.ts (1)
bots(3-4760)
apps/basket/src/types/track.ts (1)
packages/rpc/src/trpc.ts (1)
t(27-32)
apps/api/src/query/builders/traffic.ts (2)
apps/api/src/query/types.ts (1)
SimpleQueryConfig(30-56)apps/api/src/types/tables.ts (1)
Analytics(11-19)
apps/api/src/agent/utils/ai-client.ts (1)
apps/api/src/agent/index.ts (1)
getAICompletion(12-12)
apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx (2)
apps/dashboard/hooks/use-organizations.ts (1)
useOrganizationMembers(259-364)packages/db/src/drizzle/schema.ts (1)
organization(750-761)
apps/api/src/query/constants.ts (1)
apps/api/src/query/builders/index.ts (1)
QueryBuilders(12-23)
apps/basket/src/index.ts (1)
apps/basket/src/lib/logger.ts (1)
logger(5-9)
apps/basket/src/utils/ip-geo.test.ts (1)
apps/basket/src/utils/ip-geo.ts (6)
getGeoLocation(101-150)getClientIp(152-166)parseIp(168-171)anonymizeIp(173-180)getGeo(182-190)extractIpFromRequest(192-204)
apps/api/src/query/builders/pages.ts (3)
apps/api/src/query/types.ts (1)
SimpleQueryConfig(30-56)apps/api/src/types/tables.ts (1)
Analytics(11-19)packages/db/src/clickhouse/query_builder.ts (2)
limit(322-327)offset(332-337)
apps/api/src/routes/health.ts (1)
packages/db/src/clickhouse/client.ts (1)
chQuery(121-126)
apps/basket/src/hooks/auth.test.ts (1)
apps/basket/src/hooks/auth.ts (6)
normalizeDomain(151-174)isSubdomain(182-190)isValidDomainFormat(197-223)isValidOrigin(113-142)isValidOriginSecure(226-298)isLocalhost(305-311)
apps/api/src/types/tables.ts (1)
packages/db/src/clickhouse/schema.ts (4)
StripePaymentIntent(355-375)StripeCharge(377-397)StripeRefund(399-412)BlockedTraffic(414-440)
apps/dashboard/app/(main)/organizations/page.tsx (2)
apps/dashboard/app/(main)/organizations/[slug]/components/teams-tab.tsx (1)
TeamsTab(13-73)apps/dashboard/lib/utils.ts (1)
cn(4-6)
apps/api/src/query/builders/summary.ts (3)
apps/api/src/query/types.ts (3)
SimpleQueryConfig(30-56)Filter(24-28)TimeUnit(22-22)apps/api/src/types/tables.ts (1)
Analytics(11-19)packages/db/src/clickhouse/query_builder.ts (2)
limit(322-327)offset(332-337)
apps/dashboard/app/(auth)/login/page.tsx (1)
packages/auth/src/client/auth-client.ts (1)
signIn(49-49)
apps/api/src/query/builders/errors.ts (2)
apps/api/src/query/types.ts (1)
SimpleQueryConfig(30-56)apps/api/src/types/tables.ts (1)
Analytics(11-19)
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx (1)
apps/database/lib/utils.ts (1)
copyToClipboard(57-65)
apps/api/src/query/builders/geo.ts (2)
apps/api/src/query/types.ts (1)
SimpleQueryConfig(30-56)apps/api/src/types/tables.ts (1)
Analytics(11-19)
apps/dashboard/app/(main)/layout.tsx (2)
packages/auth/src/auth.ts (1)
auth(28-273)apps/dashboard/next.config.ts (1)
headers(15-47)
apps/api/src/agent/prompts/agent.ts (1)
apps/api/src/agent/index.ts (2)
AIPlanSchema(8-8)comprehensiveUnifiedPrompt(10-10)
apps/api/src/agent/utils/query-executor.ts (3)
apps/api/src/agent/index.ts (1)
executeQuery(13-13)apps/api/src/query/index.ts (1)
executeQuery(32-50)packages/db/src/clickhouse/client.ts (1)
chQuery(121-126)
apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx (1)
apps/dashboard/hooks/use-organizations.ts (1)
useOrganizationMembers(259-364)
apps/api/src/query/utils.ts (4)
packages/rpc/src/utils/referrer.ts (1)
parseReferrer(19-92)packages/shared/src/lists/referrers.ts (1)
referrers(4-2682)packages/shared/src/country-codes.ts (2)
getCountryCode(270-286)getCountryName(293-305)apps/api/src/query/screen-resolution-to-device-type.ts (1)
mapScreenResolutionToDeviceType(18-57)
apps/api/src/query/index.ts (3)
apps/api/src/query/types.ts (1)
QueryRequest(58-70)apps/api/src/query/builders/index.ts (1)
QueryBuilders(12-23)apps/api/src/query/simple-builder.ts (1)
SimpleQueryBuilder(11-175)
apps/basket/src/types/payments.ts (1)
packages/rpc/src/trpc.ts (1)
t(27-32)
🔇 Additional comments (48)
apps/basket/tsconfig.json (1)
6-6: Formatting-only change is fineArrays were flattened to single-line form without altering content—no impact on the build pipeline or type discovery.
Good consistency with the repo-wide style refactor.Also applies to: 12-13
apps/dashboard/app/(main)/home/page.tsx (1)
1-5: LGTM – purely stylistic update
String-quote normalization has no functional impact.apps/dashboard/app/(auth)/login/magic-sent/page.tsx (1)
73-80: String literal inside JSX is fine – no action needed
The new'Resend magic link'literal renders correctly; no issues spotted.apps/dashboard/app/(auth)/login/forgot/page.tsx (1)
24-37: Verify client API method nameThe call uses
authClient.forgetPassword. In most auth SDKs the verb isforgotPassword. A typo here will lead to a runtimeundefined is not a function.- await authClient.forgetPassword({ + await authClient.forgotPassword({Please confirm the exported method name in
@databuddy/auth/client.apps/dashboard/app/(main)/sandbox/api-testing/page.tsx (3)
3-16: Consistent quote style & import formatting look good
The switch to single quotes and multi-line imports matches the project-wide style enforcement. No functional impact.
139-151: Good guard on conditional body field
Rendering the request-body textarea only for POST/PUT keeps the UI clean and prevents irrelevant input.
224-249: Data array reformatting is purely cosmetic
The quick-test endpoints array now reads much clearer; no behavioural change.apps/dashboard/app/(main)/revenue/_components/onboarding/step-indicator.tsx (2)
3-5: Quote normalisation confirmed
Imports switched to single quotes; no other changes.
19-23: Conditional className string remains correct
Nested ternary still produces the intended colour states; just reformatted.apps/dashboard/app/(main)/revenue/_components/onboarding/steps/overview-step.tsx (1)
14-16: Heading reflow only; semantics intact
Splitting the<h3>text across lines doesn’t affect rendering or a11y.apps/dashboard/app/(main)/revenue/_components/onboarding/steps/complete-step.tsx (1)
28-30: Heading reflow matches style guide
Purely stylistic; no functional concerns.apps/dashboard/app/(main)/revenue/utils/types.ts (1)
1-1: Type alias quote update is safe
Changing the union literals to single quotes does not affect type-checking or downstream usage.apps/dashboard/app/(main)/revenue/_components/tabs/overview-tab.tsx (1)
24-32: Looks good – purely presentational edits
No behavioural change introduced here.apps/dashboard/app/(main)/organizations/components/stats-skeleton.tsx (1)
1-1: Import-quote switch is in line with project conventions – no action required.apps/dashboard/app/(main)/organizations/[slug]/components/organization-page-skeleton.tsx (1)
1-1: Consistent import style acknowledged.apps/dashboard/app/(main)/billing/components/plans-tab.tsx (1)
1-7: Compact function body is clean and idiomatic – looks good.apps/dashboard/app/(main)/billing/components/no-payment-method-dialog.tsx (1)
16-52: Looks good – purely stylistic changesNo functional issues spotted in the dialog logic.
apps/dashboard/app/(main)/organizations/[slug]/components/overview-tab.tsx (1)
210-225: Guard against empty names when deriving avatar fallback
member.user.name.charAt(0)throws ifnameis an empty string.
Use a safe fallback:-<AvatarFallback ...> - {member.user.name.charAt(0).toUpperCase()} -</AvatarFallback> +<AvatarFallback ...> + {(member.user.name?.[0] || '?').toUpperCase()} +</AvatarFallback>apps/dashboard/app/(main)/organizations/[slug]/components/website-selector.tsx (1)
57-61: Looks good – concise toggle logic
The inline callback cleanly flips between selecting and clearing the active website ID; no functional issues spotted.apps/dashboard/app/(main)/organizations/[slug]/components/transfer-assets.tsx (1)
154-159: Tailwind arbitrary animation classes rely on the keyframes being present at build timeUsing
animate-[slide-right_1s_ease-in-out]/slide-leftinside runtime <style jsx> works in dev, but Tailwind strips unknown arbitrary values unless the keyframes are whitelisted incontentor declared intailwind.config.js.Verify the animation still works after production builds. If it doesn’t, move the
@keyframesdeclarations to a CSS file loaded at build time and add:theme: { extend: { animation: { 'slide-right': 'slide-right 1s ease-in-out', 'slide-left': 'slide-left 1s ease-in-out', }, }, }apps/dashboard/app/(main)/organizations/components/organizations-tab.tsx (1)
170-176: Icon-only buttons need accessible labelsThe trash button (and similar) has no
aria-label. Screen-reader users won’t know its purpose.<Button className="rounded …" + aria-label="Delete organization" …>Likely an incorrect or invalid review comment.
apps/dashboard/app/(main)/billing/hooks/use-billing.ts (1)
75-80: Guard against SSR usage
openBillingPortalreferenceswindow.location.origin. If this hook is ever executed in a server context it will throw. A simple guard prevents accidental SSR regression:if (typeof window === 'undefined') return; await openBillingPortal({ returnUrl: `${window.location.origin}/billing` });apps/dashboard/app/(main)/organizations/page.tsx (1)
241-259: UI state is self-contained and works as intended
MainViewcorrectly controls active tab state viauseStateand keeps theTabscomponent in sync throughvalue/onValueChange. No logic issues detected here.apps/api/package.json (1)
27-27: No-op change is fineOnly a trailing newline was added—no functional impact.
apps/basket/package.json (1)
34-34: File-ending newline addedNothing to flag.
apps/basket/src/utils/validation.ts (1)
155-167: Good defensive numeric parsingThe refactored
validateNumericreads well and correctly guards againstNaN/Infinitywhile supporting string inputs.apps/api/src/query/types.ts (1)
1-56: No concerns – purely stylistic edits
The re-indent and single-quote switch do not alter any exported types.apps/basket/src/utils/user-agent.ts (1)
102-108:Acceptheader absence ≠ botSome legitimate clients (certain fetch libraries, curl, edge-cases with service-workers) omit the
Acceptheader. Treating that alone as bot traffic risks false positives. Consider combining signals (e.g. UA length + missingAccept) or downgrading to a confidence score.apps/api/src/query/builders/custom-events.ts (1)
16-20: Looks good – purely stylisticThe
whereclause retains its original semantics; only quoting style changed.apps/api/src/query/builders/errors.ts (1)
4-7: Type parameter onSimpleQueryConfigmay be invalid
SimpleQueryConfigis imported as if it were generic, yet the definition inquery/types.tsshows no type parameter. This will fail type-checking unless there’s an overloaded declaration elsewhere.Please confirm and either:
-SimpleQueryConfig<typeof Analytics.errors> +SimpleQueryConfigor update the interface to be generic across builder files for consistency.
apps/basket/src/utils/user-agent.test.ts (1)
1-2: Mind theRequestpolyfill dependency
vitestin a Node environment doesn’t ship a globalRequestobject.
If a polyfill (e.g.undiciornode-fetch) isn’t injected in the test runner’s setup file, these tests will crash at runtime.Run the suite locally; if it explodes with
ReferenceError: Request is not defined, add:// test/setup.ts import 'undici/register' // or globalThis.Request = (await import('node-fetch')).Requestand reference this file in
vitest.config.ts.apps/api/src/query/builders/index.ts (1)
12-22: Verify no duplicate keys across QueryBuilders spreads
With ten builder objects spread intoQueryBuilders, any overlapping property name in a later spread will silently override an earlier one. Our automation scripts ran into syntax issues, so please manually confirm there are no duplicate builder keys. For example, you can run:for file in apps/api/src/query/builders/*Builders.ts; do awk '/export const .*Builders *= *{/,/};/' "$file" done \ | grep -oE "['\"]?[A-Za-z0-9_]+['\"]?(?=:)" \ | tr -d \"\' \ | sort | uniq -dIf this prints any names, rename or dedupe the conflicting builder functions to avoid silent overrides.
apps/api/src/query/builders/devices.ts (1)
95-112: EnsuremapDeviceTypesplugin exists
device_typesdeclaresplugins: { mapDeviceTypes: true }, yet no guarantee the plugin is registered in the query pipeline.
A missing plugin will throw at runtime.Search for its implementation; if absent, either add it or drop the flag.
apps/basket/src/types/payments.ts (1)
1-1: Typings only – OKImporting
tfromelysiais correct for TypeBox-backed schemas; no functional changes introduced.apps/basket/src/utils/event-schema.ts (1)
1-1: Standardise the Zod import pathThis file imports from
'zod/v4'while other API-side files import'zod'. Pick one convention to avoid duplicate bundles and subtle type-identity issues.apps/api/src/query/builders/traffic.ts (1)
4-7: Verify thatSimpleQueryConfigis declared as a generic
SimpleQueryConfig<typeof Analytics.events>impliesSimpleQueryConfigis generic, but the snippet inquery/types.tsshows a non-generic interface. If the interface really takes zero type parameters, this will be a compile-time error.apps/api/src/query/builders/geo.ts (1)
5-90: LGTM – purely stylistic changesAll modifications are quote/indentation normalisation; no functional impact detected.
apps/api/src/routes/assistant.ts (1)
1-96: Excellent stylistic consistency improvements!The reformatting enhances code readability through consistent single quote usage, proper indentation, and organized import statements. All functional logic remains intact.
Note: The PR objectives mention enforcing "double quotes" but the implementation uses single quotes throughout. This appears to be a documentation discrepancy rather than an implementation issue.
apps/api/src/query/constants.ts (1)
1-237: Consistent stylistic improvements with a minor logic enhancement.The formatting changes improve code consistency with single quote usage, proper indentation, and trailing commas. The simplification of the
isQueryCustomizablefunction (lines 230-233) to directly return the property value is a nice improvement over the previous|| falsepattern.apps/api/src/query/builders/pages.ts (1)
1-171: Well-executed formatting improvements throughout the query builders.The consistent use of single quotes, improved indentation, and reformatted SQL queries significantly enhance readability. All query logic, configurations, and exported functionality remain unchanged.
apps/api/src/agent/handlers/metric-handler.ts (1)
1-89: Clean formatting improvements that enhance code organization.The reformatted imports, consistent indentation, and improved parameter alignment make the code more readable. All metric handling logic, SQL validation, and error handling functionality remain intact.
apps/api/src/query/builders/performance.ts (1)
1-188: Consistent formatting improvements across all performance query builders.The standardization to single quotes, improved indentation, and consistent trailing commas enhance readability. All performance metrics calculations, filtering logic, and query configurations remain functionally unchanged.
apps/api/src/query/builders/summary.ts (1)
4-8: ✅ Purely stylistic – looks goodThe reshaped generic type declaration and multiline object literal improve readability without affecting behaviour. No concerns.
apps/api/src/query/builders/sessions.ts (1)
18-26: ✅ No-op formatting changeQuote‐style normalisation and trailing commas are syntactically correct and keep the builder consistent with the rest of the codebase.
apps/api/src/index.ts (1)
10-20: CORS origin list correctly gated by NODE_ENVThe regex plus dev override is clear and avoids inadvertent prod exposure. Good defensive choice.
apps/api/src/agent/handlers/chart-handler.ts (1)
49-63: LGTM – logic untouchedFormatting only; the early-return guard, SQL validation, and admin debug info remain intact.
apps/api/src/agent/index.ts (1)
1-20: Export regrouping looks goodPure re-ordering / grouping – no functional impact. Tree-shaking and type exports remain intact.
apps/api/src/agent/prompts/agent.ts (1)
4-20: Keep enum in sync with downstream consumers
chart_typenow includesradar,funnel,grouped_bar. Please verify that:
- Chart-handler front-end components render these types.
- Any runtime validation (e.g. ClickHouse result-shape mappers) recognises them.
If not, add fallbacks before this ships.
| userQuery: string, | ||
| websiteId: string, | ||
| websiteHostname: string, | ||
| mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step', | ||
| previousMessages?: any[], | ||
| agentToolResult?: any, | ||
| model?: 'chat' | 'agent' | 'agent-max' | ||
| ) => ` |
There was a problem hiding this comment.
model parameter is unused – will fail with noUnusedParameters
The new optional model argument is never referenced. If the repo has noUnusedParameters turned on, compilation will fail.
Either remove it or wire it into the template.
- agentToolResult?: any,
- model?: 'chat' | 'agent' | 'agent-max'
+ agentToolResult?: anyor make use of it inside the template.
📝 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.
| userQuery: string, | |
| websiteId: string, | |
| websiteHostname: string, | |
| mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step', | |
| previousMessages?: any[], | |
| agentToolResult?: any, | |
| model?: 'chat' | 'agent' | 'agent-max' | |
| ) => ` | |
| userQuery: string, | |
| websiteId: string, | |
| websiteHostname: string, | |
| mode: 'analysis_only' | 'execute_chat' | 'execute_agent_step', | |
| previousMessages?: any[], | |
| agentToolResult?: any | |
| ) => ` |
🤖 Prompt for AI Agents
In apps/api/src/agent/prompts/agent.ts around lines 37 to 44, the optional
'model' parameter is declared but not used, which will cause a compilation error
with 'noUnusedParameters' enabled. To fix this, either remove the 'model'
parameter if it is unnecessary, or incorporate it into the template string where
the function returns, ensuring it is referenced and used properly.
| const OPENAI_CONFIG = { | ||
| apiKey: process.env.AI_API_KEY, | ||
| baseURL: 'https://openrouter.ai/api/v1', | ||
| apiKey: process.env.AI_API_KEY, | ||
| baseURL: 'https://openrouter.ai/api/v1', | ||
| } as const; | ||
|
|
||
| const AI_MODEL = 'google/gemini-2.5-flash-lite-preview-06-17'; | ||
|
|
||
| const openai = new OpenAI(OPENAI_CONFIG); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fail fast when AI_API_KEY is missing
Instantiation happens at module load; if the env var is absent the SDK will later throw. Surfacing this earlier helps ops triage:
-const OPENAI_CONFIG = {
- apiKey: process.env.AI_API_KEY,
- baseURL: 'https://openrouter.ai/api/v1',
-} as const;
+if (!process.env.AI_API_KEY) {
+ throw new Error('Environment variable AI_API_KEY is not set');
+}
+
+const OPENAI_CONFIG = {
+ apiKey: process.env.AI_API_KEY,
+ baseURL: 'https://openrouter.ai/api/v1',
+} as const;📝 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.
| const OPENAI_CONFIG = { | |
| apiKey: process.env.AI_API_KEY, | |
| baseURL: 'https://openrouter.ai/api/v1', | |
| apiKey: process.env.AI_API_KEY, | |
| baseURL: 'https://openrouter.ai/api/v1', | |
| } as const; | |
| const AI_MODEL = 'google/gemini-2.5-flash-lite-preview-06-17'; | |
| const openai = new OpenAI(OPENAI_CONFIG); | |
| if (!process.env.AI_API_KEY) { | |
| throw new Error('Environment variable AI_API_KEY is not set'); | |
| } | |
| const OPENAI_CONFIG = { | |
| apiKey: process.env.AI_API_KEY, | |
| baseURL: 'https://openrouter.ai/api/v1', | |
| } as const; | |
| const AI_MODEL = 'google/gemini-2.5-flash-lite-preview-06-17'; | |
| const openai = new OpenAI(OPENAI_CONFIG); |
🤖 Prompt for AI Agents
In apps/api/src/agent/utils/ai-client.ts around lines 3 to 10, the code
initializes the OpenAI client using an API key from the environment without
checking if the key is present. To fail fast when AI_API_KEY is missing, add a
check right after reading the environment variable to verify it is defined. If
it is missing, throw an explicit error immediately before creating the OpenAI
instance to help with early detection and easier troubleshooting.
| data: unknown[]; | ||
| executionTime: number; | ||
| rowCount: number; | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Introduce a generic to preserve result typing
QueryResult.data is typed as unknown[], which throws away everything that chQuery’s generic gives you. Expose that information by parameterising both the interface and executeQuery().
-export interface QueryResult {
- data: unknown[];
+export interface QueryResult<T = unknown> {
+ data: T[];
executionTime: number;
rowCount: number;
}
-export async function executeQuery(sql: string): Promise<QueryResult> {
+export async function executeQuery<T = unknown>(sql: string): Promise<QueryResult<T>> {
const queryStart = Date.now();
- const result = await chQuery(sql);
+ const result = await chQuery<T>(sql);This is a zero-risk change for current callers (type argument infers to unknown) while giving stronger typing to new code.
📝 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.
| data: unknown[]; | |
| executionTime: number; | |
| rowCount: number; | |
| } | |
| export interface QueryResult<T = unknown> { | |
| data: T[]; | |
| executionTime: number; | |
| rowCount: number; | |
| } | |
| export async function executeQuery<T = unknown>(sql: string): Promise<QueryResult<T>> { | |
| const queryStart = Date.now(); | |
| const result = await chQuery<T>(sql); | |
| // …rest of implementation remains unchanged | |
| } |
🤖 Prompt for AI Agents
In apps/api/src/agent/utils/query-executor.ts around lines 4 to 8, the
QueryResult interface currently types the data property as unknown[], losing the
specific type information from chQuery's generic. To fix this, make the
QueryResult interface generic by adding a type parameter that defaults to
unknown, and use this parameter to type the data array. Also, update the
executeQuery() function to be generic and return a QueryResult with the
corresponding type parameter. This preserves existing behavior while enabling
stronger typing for new code.
| timeTaken: `${queryTime}ms`, | ||
| resultCount: result.length, | ||
| sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''), | ||
| }); |
There was a problem hiding this comment.
Avoid leaking full SQL in production logs
Even truncated to 100 chars, raw SQL can expose PII or internal schema. Wrap the console.info behind a debug flag or redact parameters before logging.
-console.info('🔍 [Query Executor] Query completed', {
- timeTaken: `${queryTime}ms`,
- resultCount: result.length,
- sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
-});
+if (process.env.NODE_ENV === 'development') {
+ console.info('🔍 [Query Executor] Query completed', {
+ timeTaken: `${queryTime}ms`,
+ resultCount: result.length,
+ sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
+ });
+}📝 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.
| timeTaken: `${queryTime}ms`, | |
| resultCount: result.length, | |
| sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''), | |
| }); | |
| if (process.env.NODE_ENV === 'development') { | |
| console.info('🔍 [Query Executor] Query completed', { | |
| timeTaken: `${queryTime}ms`, | |
| resultCount: result.length, | |
| sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''), | |
| }); | |
| } |
🤖 Prompt for AI Agents
In apps/api/src/agent/utils/query-executor.ts around lines 15 to 18, the current
code logs raw SQL queries truncated to 100 characters, which can still expose
sensitive information. Modify the logging to either wrap the console.info call
behind a debug flag so it only logs in development or debug mode, or redact
sensitive parameters from the SQL string before logging to avoid leaking PII or
internal schema details.
| const { inviteMember, isInvitingMember } = | ||
| useOrganizationMembers(organizationId); | ||
|
|
There was a problem hiding this comment.
awaiting mutate is a bug – switch to mutateAsync or drop await
useOrganizationMembers returns inviteMemberMutation.mutate, whose return type is void.
await inviteMember(...) therefore resolves immediately and does not wait for the network request, which defeats the loading state and may surface unhandled errors.
-const { inviteMember, isInvitingMember } =
- useOrganizationMembers(organizationId);
+const {
+ inviteMember: inviteMemberRaw,
+ isInvitingMember,
+} = useOrganizationMembers(organizationId);
+
+// Prefer the async variant if you need to await
+const inviteMember = inviteMemberRaw as unknown as (
+ Parameters<typeof inviteMemberRaw>[0]
+) => Promise<unknown>;or simpler:
-await inviteMember({ ... });
+inviteMember({ ... }); // fire-and-forgetConsider exporting mutateAsync from the hook for cleaner semantics.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/organizations/[slug]/components/invite-member-dialog.tsx
around lines 29 to 31, the code is incorrectly awaiting the mutate function
returned by useOrganizationMembers, which returns void and does not wait for the
network request. To fix this, update the hook to export mutateAsync instead of
mutate, then replace await inviteMember(...) with await inviteMemberAsync(...)
to properly await the network request and handle loading and errors correctly.
| if (!(imageSrc && completedCrop && imageRef.current)) { | ||
| toast.error('Please crop the image before uploading.'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Validate crop dimensions before upload
completedCrop can be truthy while still having width === 0 or height === 0 (e.g., user closes the modal without moving the crop handles). A zero-sized crop will cause getCroppedImage to throw.
- if (!(imageSrc && completedCrop && imageRef.current)) {
+ if (
+ !(
+ imageSrc &&
+ completedCrop &&
+ completedCrop.width > 0 &&
+ completedCrop.height > 0 &&
+ imageRef.current
+ )
+ ) {📝 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.
| if (!(imageSrc && completedCrop && imageRef.current)) { | |
| toast.error('Please crop the image before uploading.'); | |
| return; | |
| } | |
| if ( | |
| !( | |
| imageSrc && | |
| completedCrop && | |
| completedCrop.width > 0 && | |
| completedCrop.height > 0 && | |
| imageRef.current | |
| ) | |
| ) { | |
| toast.error('Please crop the image before uploading.'); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/organizations/[slug]/components/organization-logo-uploader.tsx
around lines 100 to 103, the current check only verifies if completedCrop is
truthy but does not validate its dimensions. Update the condition to also check
that completedCrop.width and completedCrop.height are greater than zero before
proceeding. If either dimension is zero, show the error toast and return early
to prevent getCroppedImage from throwing due to invalid crop size.
| <FloppyDiskBackIcon | ||
| className="h-4 w-4" | ||
| size={16} | ||
| weight="fill" | ||
| /> | ||
| {isSaving ? 'Saving...' : 'Save Changes'} | ||
| </Button> |
There was a problem hiding this comment.
Disable save while secret empty
Prevent empty secrets from being persisted.
- disabled={isSaving}
+ disabled={isSaving || !localWebhookSecret.trim()}🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx
around lines 140 to 146, the save button should be disabled when the secret is
empty to prevent saving invalid data. Add a condition to the Button component's
disabled property that checks if the secret value is empty or invalid, ensuring
the button is disabled in that case.
| window.open('https://dashboard.stripe.com/webhooks', '_blank') | ||
| } | ||
| size="sm" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Harden window.open call
Add noopener,noreferrer to block reverse-tab-nabbing.
- window.open('https://dashboard.stripe.com/webhooks', '_blank')
+ window.open(
+ 'https://dashboard.stripe.com/webhooks',
+ '_blank',
+ 'noopener,noreferrer'
+ )📝 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.
| window.open('https://dashboard.stripe.com/webhooks', '_blank') | |
| } | |
| size="sm" | |
| window.open( | |
| 'https://dashboard.stripe.com/webhooks', | |
| '_blank', | |
| 'noopener,noreferrer' | |
| ) | |
| } | |
| size="sm" |
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx
around lines 172 to 174, the window.open call lacks security attributes. Update
the window.open call to include 'noopener,noreferrer' in the third argument to
prevent reverse-tab-nabbing attacks by adding "noopener,noreferrer" as the
window features string.
| <Button | ||
| className="flex-1" | ||
| disabled={!localWebhookSecret || hasChanges} | ||
| onClick={onNext} | ||
| > |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Block navigation during save
While the mutation is in flight, disable the “Continue” button to avoid state desync.
- disabled={!localWebhookSecret || hasChanges}
+ disabled={!localWebhookSecret || hasChanges || isSaving}📝 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.
| <Button | |
| className="flex-1" | |
| disabled={!localWebhookSecret || hasChanges} | |
| onClick={onNext} | |
| > | |
| <Button | |
| className="flex-1" | |
| disabled={!localWebhookSecret || hasChanges || isSaving} | |
| onClick={onNext} | |
| > |
🤖 Prompt for AI Agents
In
apps/dashboard/app/(main)/revenue/_components/onboarding/steps/webhook-step.tsx
around lines 192 to 196, the "Continue" button should be disabled while the
mutation is in progress to prevent state desynchronization. Update the disabled
prop to also check if the mutation is loading or in flight, and disable the
button accordingly until the save operation completes.
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
This pull request introduces several improvements to the codebase, focusing on consistent formatting, enhanced configuration, and minor functional adjustments. The most significant changes include enforcing consistent double-quote usage across the project, updating editor settings for code formatting, and refining schema definitions for better clarity.
Configuration and Formatting Enhancements:
.vscode/settings.json: Added settings for default formatters, enabledformatOnSaveandformatOnPaste, and configuredbiomefor code actions on save.apps/api/biome.json: Simplified theincludesarray in thefilessection for better readability.Code Style Consistency:
apps/api/src/agent/handlers/chart-handler.ts[1] [2] [3]apps/api/src/agent/handlers/metric-handler.ts[1] [2] [3]apps/api/src/agent/index.tsapps/api/src/agent/processor.ts[1] [2] [3] [4]apps/api/src/agent/prompts/agent.ts[1] [2] [3]apps/api/src/agent/utils/ai-client.tsSchema and Prompt Refinements:
apps/api/src/agent/prompts/agent.ts: ImprovedAIResponseJsonSchemaandAIPlanSchemadefinitions for better clarity and alignment with coding standards.comprehensiveUnifiedPromptfunction to handle conversation history and agent tool results more cleanly.These changes collectively improve the maintainability, readability, and consistency of the codebase.
Summary by CodeRabbit
Style
Documentation
Chores
No functional changes or new features were introduced. All updates are non-disruptive and focused on code quality and maintainability.