Conversation
📝 WalkthroughWalkthroughThis PR introduces a comprehensive multi-tenant KYC (Know Your Customer) system integrated with Clerk authentication and Dr. Green API. It adds server actions for user verification, consultation submission with tenant-aware data access, order validation, enhanced product details with currency and discount support, and refactored cart/checkout flows with preloaded shipping addresses. Changes
Sequence DiagramssequenceDiagram
participant User as Client / Browser
participant App as Next.js App
participant Clerk as Clerk Auth
participant DB as Database
participant DrGreen as Dr. Green API
User->>App: Submits consultation form
App->>Clerk: Create/verify user
Clerk-->>App: Clerk user ID
App->>DB: Create local user (Clerk ID)
DB-->>App: Local user created
App->>DB: Create consultation_questionnaires entry
DB-->>App: Questionnaire stored
App->>DrGreen: Submit questionnaire payload (signed)
DrGreen-->>App: clientId + kycLink
App->>DB: Update questionnaire with clientId
App->>DB: Update user with drGreenClientId & tenantId
DB-->>App: Record updated
App-->>User: Success (or queued for retry)
sequenceDiagram
participant User as Client / Browser
participant App as Next.js App
participant DB as Database
participant DrGreen as Dr. Green API
User->>App: Request order submission
App->>DB: Query consultation_questionnaires (local KYC check)
alt Local verification found
DB-->>App: Verified questionnaire
App->>App: Use local verification
else No local verification
App->>DB: Check user.drGreenClientId
DB-->>App: drGreenClientId
App->>DrGreen: Fetch client status
DrGreen-->>App: Client status (ACTIVE & verified)
end
App->>DB: Create order + order_items
DB-->>App: Order persisted
alt Has drGreenCartId & no local override
App->>DrGreen: Delete external cart
end
App-->>User: Order confirmation
sequenceDiagram
participant User as Client / Browser
participant App as Next.js App
participant DB as Database
participant Clerk as Clerk Auth
participant DrGreen as Dr. Green API
User->>App: Load dashboard/products
App->>Clerk: Get current user
Clerk-->>App: Clerk user or null
App->>DB: Lookup user by Clerk ID (fallback: email)
DB-->>App: DB user (or null)
alt User found
App->>DB: Query consultation_questionnaires (verified)
DB-->>App: Questionnaire or null
alt Questionnaire exists
App->>App: Return ACTIVE + kycVerified=true
else No verified questionnaire
App->>DB: Check drGreenClientId
alt drGreenClientId exists
App->>DrGreen: Fetch client details
DrGreen-->>App: Client status & verified flag
App->>App: Return status based on client
else No drGreenClientId
App->>App: Return PENDING or NO_ID
end
end
else User not found
App->>App: Return GUEST
end
App-->>User: KYC status + UI state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
nextjs_space/app/api/consultation/submit/route.ts (1)
317-336: Remove or gate sensitive payload logging.This logs PII and medical data (HIPAA/PII risk) on every submission. Please remove or strictly gate behind a debug flag with redaction.
🧹 Proposed fix (dev-only, redacted)
- console.log("\n=== DR GREEN API DEBUG ==="); - console.log("API URL:", `${drGreenApiUrl}/dapp/clients`); - console.log("Has API Key:", !!apiKey); - console.log("Has Secret Key:", !!secretKey); - console.log("\n=== PHONE FIELDS DEBUG ==="); - console.log("Raw phoneCode from form:", JSON.stringify(body.phoneCode)); - console.log( - "Raw phoneNumber from form:", - JSON.stringify(body.phoneNumber), - ); - console.log( - "Cleaned phoneCode:", - JSON.stringify(body.phoneCode.replace(/[^\+\d]/g, "")), - ); - console.log( - "Cleaned contactNumber:", - JSON.stringify(body.phoneNumber.replace(/\D/g, "")), - ); - console.log("\n=== PAYLOAD ==="); - console.log(JSON.stringify(drGreenPayload, null, 2)); - console.log("\n===================\n"); + if (process.env.NODE_ENV === "development") { + console.log("\n=== DR GREEN API DEBUG ==="); + console.log("API URL:", `${drGreenApiUrl}/dapp/clients`); + console.log("Has API Key:", !!apiKey); + console.log("Has Secret Key:", !!secretKey); + console.log("\n=== PHONE FIELDS DEBUG (redacted) ==="); + console.log("Has phoneCode:", !!body.phoneCode); + console.log("Has phoneNumber:", !!body.phoneNumber); + console.log("\n===================\n"); + }nextjs_space/app/store/[slug]/cart/page.tsx (1)
197-215: Guard against mixed-currency carts in totals.Lines 197 and 215 derive subtotal/total currency from
items[0]. Because the cart store isn’t tenant-scoped, a user can end up with items from multiple stores/currencies, which makes totals and labels incorrect. Consider enforcing single-currency carts (e.g., clear cart on store change) or grouping totals per currency.nextjs_space/app/store/[slug]/checkout/page.tsx (1)
242-274: Handle mixed-currency carts and align fallback currency.Lines 242–274 assume a single currency (using
items[0]?.currency) and default to'$'. If the cart contains items from another store/currency, totals and labels become incorrect; also, the fallback differs from the cart page (“€”).Consider enforcing a single currency per cart or grouping totals by currency, and use a consistent default (preferably derived from tenant config).
nextjs_space/lib/doctor-green-api.ts (1)
252-257: Image base should respect tenant apiUrlNow that per‑tenant API URLs exist, image normalization should use
config.apiUrl(when present) rather than the globalAPI_URL, or images will break for tenants using custom endpoints.🛠️ Suggested fix to respect tenant apiUrl for image base
- const apiBase = API_URL.replace(/\/api\/v1\/?$/, ''); + const apiBase = (config?.apiUrl || API_URL).replace(/\/api\/v1\/?$/, '');- const apiBase = API_URL.replace(/\/api\/v1\/?$/, ''); + const apiBase = (config?.apiUrl || API_URL).replace(/\/api\/v1\/?$/, '');Also applies to: 329-332
nextjs_space/app/store/[slug]/products/[id]/page.tsx (1)
239-246: Guard Next/Image against emptysrcvaluesNext.js Image component throws an error when
src=""is passed. Currently, ifproduct.strainImagesexists but contains items withoutstrainImageUrl, empty strings will be passed to the Image component at lines 300 and 366. FilterstrainImagesto only valid images and ensure fallback URLs exist before rendering.🖼️ Safer gallery handling
const imageUrl = product.image_url || product.imageUrl; + const galleryImages = + (product.strainImages ?? []).filter((img) => img.strainImageUrl);- {((product.strainImages && product.strainImages.length > 0) || imageUrl) ? ( + {(galleryImages.length > 0 || imageUrl) ? ( <div className="absolute inset-0 p-8 flex items-center justify-center"> <div className="relative w-full h-full transition-transform duration-500 hover:scale-110"> <Image src={ - (product.strainImages && product.strainImages.length > 0) - ? (product.strainImages[selectedImage]?.strainImageUrl || "") - : (imageUrl || "") + galleryImages.length > 0 + ? (galleryImages[selectedImage]?.strainImageUrl || imageUrl || "") + : (imageUrl || "") } alt={product.name} fill className="object-contain drop-shadow-2xl" sizes="(max-width: 768px) 100vw, 50vw" priority /> </div> </div> ) : (- {product.strainImages && product.strainImages.length > 1 && ( + {galleryImages.length > 1 && ( <div className="flex gap-4 overflow-x-auto pb-2"> - {product.strainImages.map((img, idx) => ( + {galleryImages.map((img, idx) => ( <button key={idx} onClick={() => setSelectedImage(idx)} className={`relative w-24 h-24 rounded-lg overflow-hidden border-2 transition-all flex-shrink-0 ${selectedImage === idx ? "border-primary" : "border-transparent opacity-70 hover:opacity-100" }`} style={{ borderColor: selectedImage === idx ? "var(--tenant-color-primary, `#059669`)" : "transparent" }} > <Image - src={img.strainImageUrl || ""} + src={img.strainImageUrl!} alt={img.altText || `View ${idx + 1}`} fill className="object-cover" /> </button> ))} </div> )}
🤖 Fix all issues with AI agents
In `@nextjs_space/app/actions/get-user-shipping.ts`:
- Around line 46-53: The questionnaire lookup in get-user-shipping.ts uses
prisma.consultation_questionnaires.findFirst filtering only by email which can
cross tenants; update the query in get-user-shipping (the
prisma.consultation_questionnaires.findFirst call) to also filter by tenantId
(use dbUser.tenantId or request tenant context like in kyc-check.ts) and add a
guard to return/throw if tenantId or email is missing before querying; mirror
the tenant-scoping pattern used in kyc-check.ts to ensure only the current
tenant's questionnaire is returned.
In `@nextjs_space/app/actions/kyc-check.ts`:
- Around line 36-52: The questionnaire lookup can match across tenants because
tenantId is undefined when dbUser is null; change the logic in
nextjs_space/app/actions/kyc-check.ts so before calling
prisma.consultation_questionnaires.findFirst you compute a concrete tenantId
(e.g., const tenantId = dbUser?.tenantId ?? <authenticated user's tenant id>)
and verify an email exists (guard clerkUser.email), then pass where: { tenantId,
email: { equals: clerkUser.email, mode: 'insensitive' } } to
prisma.consultation_questionnaires.findFirst; ensure you bail/return
appropriately if tenantId or email is missing to avoid cross-tenant leaks.
In `@nextjs_space/app/api/store/`[slug]/orders/submit/route.ts:
- Around line 63-72: The local KYC override query
(prisma.consultation_questionnaires.findFirst) only filters by email and
isKycVerified, allowing a verified questionnaire from another tenant to be used;
update the query for localQuestionnaire to include tenantId: tenant.id in the
where/AND filters so the lookup is scoped to the current tenant (use the
existing localQuestionnaire variable and prisma.consultation_questionnaires call
and add tenant.id to the AND conditions).
In `@nextjs_space/app/store/`[slug]/orders/[orderId]/page.tsx:
- Around line 130-145: The UI currently hardcodes "ZAR" for line-item, subtotal,
shipping and total labels; replace those hardcoded strings with a dynamic
currency value (use order.currency if present, otherwise derive from an item
like items[0]?.currency or item.currency inside the loop) so the displayed
currency matches the order data; update the spans rendering item price, subtotal
(order.subtotal), shippingCost (order.shippingCost) and total (order.total) to
prepend the resolved currency variable instead of "ZAR" (see variables order,
item and the cart-dropdown.tsx pattern items[0]?.currency for reference).
In `@nextjs_space/app/store/`[slug]/products/page.tsx:
- Line 343: The product listing renders product.currency directly which can be
undefined for legacy products; update the render logic around product.currency
(the expression that currently outputs "{product.currency}
{product.price.toFixed(2)}") to use a fallback such as tenantCurrency or a
hardcoded "€" when product.currency is falsy (e.g., use product.currency ||
tenantCurrency || '€'), ensuring the rest of the display
(product.price.toFixed(2)) remains unchanged.
In `@nextjs_space/debug-deep-output.txt`:
- Around line 7-28: The debug dump file nextjs_space/debug-deep-output.txt
contains PHI/PII (emails, DOBs, addresses, phone numbers, tenant IDs, password
hashes, medicalConditions) and must be removed and replaced with anonymized
fixtures: delete the sensitive file from the commit, replace it with a synthetic
fixture containing non-identifiable example rows for the
consultation_questionnaires schema, redact all fields (email, dateOfBirth,
phoneCode/phoneNumber, address*, tenantId, password, medicalConditions,
drGreenClientId, submissionError) in the fixture, and then purge the sensitive
file from the repository history (use a history-rewrite tool) so no sensitive
data remains in commits.
- Around line 1-6: The file nextjs_space/debug-deep-output.txt contains
committed PII (emails like gerard161+buds@gmail.com, password/resetToken fields,
addresses, phone numbers) — remove the sensitive data by deleting the file from
the repo history or replacing it with a redacted placeholder that omits/masks
all PII (mask email, remove password/resetToken, phone/address lines) and keep
only non-sensitive debug metadata; add a rule to .gitignore to prevent future
commits of debug-deep-output.txt (or a broader pattern like nextjs_space/*.txt
or debug*.txt); if the file was already committed, run git rm --cached
nextjs_space/debug-deep-output.txt and commit the removal (or perform a history
rewrite if required) and, if any secrets/credentials were exposed, rotate them
immediately.
In `@nextjs_space/debug-output.txt`:
- Around line 1-9: The committed debug output contains PII (email, user ID, SQL
traces) in nextjs_space/debug-output.txt; remove or redact it, add
nextjs_space/debug-output.txt (or its folder) to .gitignore, replace the file
with a sanitized placeholder if you must keep a sample, and purge the sensitive
data from history (use a git history-rewrite tool to remove the file from prior
commits and force-push). Also search the repo for other debug logs containing
emails/IDs and remove or redact them before pushing.
In `@nextjs_space/lib/drgreen-orders.ts`:
- Around line 77-101: The guard that throws when user.drGreenClientId is missing
prevents the local override path from running; update the logic around
localQuestionnaire and drGreenClientId so the missing-clientId check only
applies to the API call path: relocate or change the existing throw to live
inside the else branch that calls callDrGreenAPI (or add a conditional that only
throws if !localQuestionnaire), ensuring localQuestionnaire still constructs the
mock orderData (id/invoiceNumber/status/total) without requiring
drGreenClientId.
- Around line 66-75: The local KYC lookup using
prisma.consultation_questionnaires.findFirst (assigned to localQuestionnaire)
currently filters only by email and can leak across tenants; update the where
clause to also filter by tenantId (or the user's tenant linkage, e.g.,
user.tenantId) so the query becomes constrained by both email (case-insensitive)
and tenantId, ensuring the local verification is scoped to the same tenant as
the current user.
In `@nextjs_space/scripts/debug-kyc-auth.ts`:
- Line 9: Remove the hardcoded constant KEY and replace it with reading a secure
environment variable (e.g., process.env.KYC_ENCRYPTION_KEY) wherever KEY is used
(search for the constant KEY and any decrypt/encrypt calls in this file and
related helpers); update initialization in debug-kyc-auth.ts so it throws a
clear error if the env var is missing, and ensure all functions referencing KEY
(decryptTenantCredentials, decrypt, etc.) use the env var value instead; after
code change, remove the committed literal from history and coordinate a
production key rotation and update of the KYC_ENCRYPTION_KEY environment
variable in deployment configuration.
In `@nextjs_space/scripts/debug-kyc-deep.ts`:
- Around line 5-8: The script currently hard-codes sensitive PII (e.g., the
constants email and clerkId and other similar values later in the file) and logs
full records; change these to accept inputs via environment variables or CLI
arguments (process.env.DEBUG_KYC_EMAIL / DEBUG_KYC_CLERK_ID or argv), validate
they exist before use, and remove hard-coded defaults; when logging, never print
full records—log only minimal, non-sensitive fields or a redacted form (e.g.,
show first/last 3 chars or hashes) and ensure any debug output explicitly
redacts or truncates clerkId/email; finally update README or script help to
document the new env/CLI flags and ensure no PII remains in committed scripts.
- Around line 49-55: The catch block currently calls process.exit(1) which can
terminate the process before the finally block runs and skip cleanup; change the
catch to set process.exitCode = 1 instead of calling process.exit(1) so that the
finally block (where prisma.$disconnect() is awaited) always executes; locate
the main() invocation and its .catch handler and replace the hard exit with
setting exitCode while preserving the existing console.error(e) call.
In `@nextjs_space/scripts/debug-kyc-status.ts`:
- Around line 5-7: Replace the hard-coded email in debug-kyc-status.ts (the
email constant used for logging) with a parameterized value read from
process.env (e.g., process.env.DEBUG_EMAIL) or from command-line args
(process.argv) and if neither is provided log a clear error and exit with
non-zero status (use console.error and process.exit(1)) so the script fails fast
when the email is missing.
In `@nextjs_space/scripts/populate-cart.ts`:
- Around line 4-13: In main(), remove the hard-coded PII in the email variable
and instead read the user identifier from a secure source (e.g.,
process.env.POPULATE_CART_EMAIL or a CLI arg parsed in main), validate it exists
and if missing fail fast with a clear error message before calling
prisma.users.findUnique; update references to the existing email constant so the
script is reusable and safe (refer to the email variable and main() function).
In `@nextjs_space/scripts/revert-tenant-country.ts`:
- Around line 17-19: The current promise chain uses main().catch(console.error)
which only logs errors and leaves the process exit code zero; update the catch
to capture the thrown error from main, log it (using console.error or a logger)
and then call process.exit(1) to set a non‑zero exit code on failure (ensure
prisma.$disconnect() still runs in finally or await disconnect before exiting);
locate this logic around the main() invocation and prisma.$disconnect() call and
modify the catch handler to log the error and invoke process.exit(1).
In `@nextjs_space/scripts/test-drgreen-auth.ts`:
- Around line 6-46: The file contains hard-coded secrets (KEY, encryptedApiKey,
encryptedSecretKey) used by getKeyId(), decryptText(), and generateSignature();
remove these literals and load them from environment variables instead (e.g.,
process.env.DRGREEN_KEY, process.env.DRGREEN_ENCRYPTED_API_KEY,
process.env.DRGREEN_ENCRYPTED_SECRET_KEY), update getKeyId() to derive the key
from the env var, change any callers of decryptText() and generateSignature() to
pass or read the env values, add clear validation and error messages when the
env vars are missing, delete the hard-coded strings from the source, ensure
.env.example documents the new variables, and rotate/revoke the embedded
credentials in secrets management/out-of-band after deployment.
In `@nextjs_space/scripts/test-drgreen-products.ts`:
- Around line 5-28: The code currently hardcodes KEY, encryptedApiKey, and
encryptedSecretKey and uses getKeyId()/decryptText() to derive and decrypt them;
rotate the exposed API credentials immediately, remove KEY and the encrypted
payload constants from the source, and load the decryption key and ciphertexts
from secure environment variables or a secrets manager (e.g.,
process.env.DECRYPTION_KEY, process.env.ENCRYPTED_API_KEY,
process.env.ENCRYPTED_SECRET_KEY) instead; update getKeyId() and decryptText()
to read those env vars, add runtime guards that throw a clear error if required
env vars are missing, and scrub the old values from git history (and add a note
to rotate any leaked credentials).
🧹 Nitpick comments (14)
nextjs_space/scripts/revert-tenant-country.ts (2)
2-3: Align module import style with TS conventions (or confirm CJS is intended).This is a
.tsscript but usesrequire. If the repo is ESM/TS-first, considerimport { PrismaClient } from "@prisma/client";for consistency and type tooling.
5-12: Add a safety guard + parameterize the tenant ID to avoid accidental prod edits.This script performs a direct mutation with a hard-coded tenant ID. Consider requiring an explicit CLI arg and a confirmation env var to avoid unintended execution against the wrong DB.
nextjs_space/templates/healingbuds/components/Navigation.tsx (1)
252-309: Guard auth UI onisLoadedto avoid brief unauthenticated render.
isSignedInis false until Clerk finishes loading, so both desktop and mobile menus can briefly show the signed‑out UI before flipping. Consider gating onisLoadedto avoid flicker.♻️ Suggested adjustment
- {isSignedIn ? ( + {isLoaded ? ( + isSignedIn ? ( <DropdownMenu> ... </DropdownMenu> - ) : ( + ) : ( <Link href={`/store/${tenant.subdomain}/login`} ... > <User className="w-6 h-6" /> </Link> - )} + ) + ) : null}- {isSignedIn ? ( + {isLoaded ? ( + isSignedIn ? ( <> ... </> - ) : ( + ) : ( <Link href={`/store/${tenant.subdomain}/login`} ... > Patient Login </Link> - )} + ) + ) : null}Also applies to: 399-432
nextjs_space/scripts/test-drgreen-products.ts (2)
37-51: Replace the placeholder signature with a real one (or omit it).Line 49–50 uses a dummy signature and the decrypted
secretKeyis otherwise unused, so this script may not validate the real auth flow. Please confirm the Dr. Green API signature requirements and generate the signature fromsecretKeyif required.
54-56: Guard against missingfetchin non‑Node‑18 runtimes.Line 55 will throw on Node versions without global
fetch. Add a guard or explicitly provide a fetch implementation.✅ Minimal guard
async function main() { + if (typeof fetch !== "function") { + throw new Error("Global fetch is unavailable. Run with Node 18+ or provide a fetch polyfill."); + } const apiKey = decryptText(encryptedApiKey);nextjs_space/lib/cart-store.ts (1)
75-80: Consider:getTotalPrice()doesn't handle mixed currencies.If items with different currencies can coexist in the cart, summing their prices produces a meaningless total. The UI currently uses the first item's currency for display, which masks this issue.
If multi-currency carts are allowed, consider either:
- Validating that all items share the same currency when adding items
- Converting to a common currency before summing
- Returning totals grouped by currency
If single-currency carts are enforced elsewhere, this is fine as-is.
nextjs_space/prisma/check-tenant.ts (1)
1-27: Debug script looks fine for its purpose.This utility script properly handles Prisma lifecycle with
$disconnectin the finally block. The hardcoded subdomain and standalone PrismaClient are acceptable for a diagnostic tool.Consider adding a comment at the top indicating this is a debug/diagnostic script, or moving it to a clearly named debug directory to prevent accidental use in production workflows.
nextjs_space/scripts/debug-cart.ts (1)
4-31: Debug script looks functional, but consider adding a development-only guard.This script logs potentially sensitive cart data (line 25: full JSON). Since debug scripts can accidentally be run against production databases, consider adding an environment check at the start:
if (process.env.NODE_ENV === 'production') { console.error('This script should not be run in production'); process.exit(1); }nextjs_space/scripts/debug-kyc-auth.ts (1)
2-6: Inconsistent module system and PrismaClient instantiation.This script uses CommonJS
require()while other debug scripts (e.g.,debug-cart.ts) use ES imports. It also creates a newPrismaClientinstead of using the shared instance.For consistency with other scripts in this directory:
♻️ Suggested refactor
-const cryptoModule = require("crypto"); -const { PrismaClient } = require("@prisma/client"); -const https = require("https"); // or fetch if node 18+ - -const prisma = new PrismaClient(); +import crypto from "crypto"; +import { prisma } from "../lib/db";nextjs_space/scripts/fix-kyc-email.ts (1)
10-15: Consider handling the case where the record doesn't exist.If the questionnaire with
targetIddoesn't exist, Prisma will throw aRecordNotFoundError. For a one-time fix script, you may want to handle this gracefully:♻️ Optional improvement
- const updated = await prisma.consultation_questionnaires.update({ - where: { id: targetId }, - data: { email: newEmail } - }); - - console.log("Updated record:", updated.email); + const existing = await prisma.consultation_questionnaires.findUnique({ + where: { id: targetId } + }); + + if (!existing) { + console.error(`Questionnaire ${targetId} not found`); + process.exit(1); + } + + const updated = await prisma.consultation_questionnaires.update({ + where: { id: targetId }, + data: { email: newEmail } + }); + + console.log("Updated record:", updated.email);nextjs_space/next.config.js (1)
19-19: LGTM! Image domain configuration is correct.The additions for
prod-profiles-backend.s3.amazonaws.comandimg.clerk.comare properly configured in bothdomainsandremotePatterns. These are legitimate external image sources for S3-hosted assets and Clerk user avatars.Note:
images.domainsis deprecated in Next.js 14+ in favor ofremotePatternsalone. Consider removing thedomainsarray in a future cleanup to use onlyremotePatterns.Also applies to: 39-50
nextjs_space/components/cart-dropdown.tsx (1)
118-122: Guard total currency when cart may contain mixed currencies.The total uses
items[0]?.currency, which can be misleading if mixed currencies are allowed. Consider enforcing a single cart currency or presenting a mixed-currency total explicitly.nextjs_space/app/store/[slug]/dashboard/page.tsx (1)
64-92: Consider a loading state while KYC status is unresolved.
kycStatusstarts asnull, so the UI immediately shows “Pending Verification” (Lines 64–92 and 246–248) before the async check finishes. A brief “Checking verification…” state would avoid a misleading flash.Also applies to: 246-248
nextjs_space/lib/doctor-green-api.ts (1)
283-294: Gate debug logging behind an env flagThe per‑product
console.logwill be noisy in production; consider guarding it with an env flag or your logger’s debug level.♻️ Example guard
- console.log(`[DrGreen Debug] ID: ${product.id} | Default: ${defaultCurrency} | Selected: ${currency} ${price}`); + if (process.env.NODE_ENV === "development") { + console.log( + `[DrGreen Debug] ID: ${product.id} | Default: ${defaultCurrency} | Selected: ${currency} ${price}`, + ); + }
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
nextjs_space/lib/drgreen-orders.ts (1)
140-152: FixincludeingetOrderto use correct relation nameorder_items.Line 206-208 includes
items: true, but the Prisma schema defines the relation asorder_items order_items[]. This causes the order items to be omitted from the result, resulting inorder.itemsbeing undefined.include: { - items: true, + order_items: true, },(The creation code at lines 140-152 correctly uses
order_itemsand matches the schema.)
🤖 Fix all issues with AI agents
In `@nextjs_space/app/api/shop/register/route.ts`:
- Around line 85-95: In the registration route where you build the payload's
medicalRecord object, the spread ...medicalRecord is placed after your explicit
mappings (dob, gender, medicalConditions, medicinesTreatments) which allows
incoming fields to overwrite them; fix this by moving the spread to the
beginning of the object literal so your explicit mappings override any values
from the incoming medicalRecord (i.e., construct medicalRecord as {
...medicalRecord, dob: personal.dateOfBirth, gender: "Not Specified",
medicalConditions: medicalRecord.conditions || [], medicinesTreatments:
medicalRecord.currentMedications || [] } and keep the existing as any cast).
- Around line 62-66: The phone parsing in route.ts (variables phone, phoneCode,
contactNumber) incorrectly assumes a fixed two‑digit country code by using
phone.slice(0, 3); replace this with a robust approach: either make ZA-only
intent explicit via a clear comment and enforce/validate ZA formats, or
integrate a library like libphonenumber-js (e.g., use parsePhoneNumber with
default region 'ZA') to derive countryCallingCode and nationalNumber, then set
phoneCode to `+${countryCallingCode}` and contactNumber to the parsed national
number; update error handling/validation for empty or invalid numbers
accordingly.
- Around line 74-82: Replace hardcoded "ZA" values with the tenant's country
code and avoid overriding parsed phone info: use the tenant object returned by
getCurrentTenant() (tenant.countryCode) as the fallback for both
phoneCountryCode and shipping.countryCode instead of the string "ZA", and ensure
phoneCountryCode does not blindly override phoneCode—either set phoneCountryCode
from the same phone parsing logic that produced phoneCode or default to
tenant.countryCode when parsing yields no explicit country code (refer to
phoneCode, phoneCountryCode, getCurrentTenant(), and shipping.countryCode to
locate the changes).
In `@nextjs_space/app/store/`[slug]/the-wire/[postSlug]/page.tsx:
- Around line 5-6: A duplicate import of ArrowLeft, Calendar, and User from
"lucide-react" was added (two identical import statements); remove the redundant
import line so those symbols are imported only once (keep a single import
statement that imports ArrowLeft, Calendar, User from "lucide-react").
- Around line 53-58: Reintroduce HTML sanitization for post.content before it's
assigned to cleanContent and rendered with dangerouslySetInnerHTML: restore the
DOMPurify import/usage and set cleanContent = DOMPurify.sanitize(post.content ||
"", { SAFE_FOR_TEMPLATES: true }) (or equivalent) so the value used in the
component's dangerouslySetInnerHTML is sanitized; ensure DOMPurify is imported
at the top of page.tsx and remove the commented-out sanitization code to avoid
returning unsanitized post.content.
In `@nextjs_space/scripts/debug-kyc-auth.ts`:
- Around line 55-68: Replace the hardcoded tenant subdomain and user email used
in prisma calls with configurable inputs: stop using the literal 'healingbuds'
in prisma.tenants.findUnique and the literal 'gerard161+buds@gmail.com' in
prisma.users.findFirst; instead read the tenant identifier and user email from
environment variables or CLI arguments (e.g., process.env.TENANT_SUBDOMAIN /
process.env.KYC_USER_EMAIL or parsed argv) before calling
decryptText(tenant.drGreenApiKey)/decryptText(tenant.drGreenSecretKey) and the
user lookup, and validate these inputs (throw a clear error if missing) so the
script is portable and avoids embedding PII.
In `@nextjs_space/scripts/populate-cart.ts`:
- Around line 16-18: The script currently logs "User or tenant not found" and
returns, which lets automation treat this as success; in populate-cart.ts update
the failure path that checks user and user.tenantId to fail fast by logging the
error via console.error (include context like user/tenant) and calling
process.exit(1) instead of returning so the process exits with a non‑zero
status; modify the block that references user and user.tenantId accordingly.
♻️ Duplicate comments (2)
nextjs_space/lib/drgreen-orders.ts (1)
48-96: Local override still blocked by earlydrGreenClientIdguard.
Line 48-50 throws before the local questionnaire path (Line 66+) can run, so manually verified users without a client ID still cannot order. Move the guard into the API branch and add a user-null check.🐛 Proposed fix
- if (!user?.drGreenClientId) { - throw new Error("User must complete consultation before placing orders"); - } + if (!user) { + throw new Error("User not found"); + }nextjs_space/app/actions/kyc-check.ts (1)
36-49: Consider falling back to auth tenantId when dbUser is missing.
Line 38 only usesdbUser?.tenantId, so users not in DB but withclerkUser.tenantIdskip the pending questionnaire check and return NO_ID. Using the auth tenantId (and returning NO_TENANT when absent) keeps behavior consistent.♻️ Suggested adjustment
- const tenantId = dbUser?.tenantId; - if (!tenantId || !clerkUser.email) { - return { isLoggedIn: true, kycVerified: false, status: "NO_ID" }; - } + const tenantId = dbUser?.tenantId ?? clerkUser.tenantId; + if (!tenantId) { + return { isLoggedIn: true, kycVerified: false, status: "NO_TENANT" }; + } + if (!clerkUser.email) { + return { isLoggedIn: true, kycVerified: false, status: "NO_ID" }; + }
🧹 Nitpick comments (4)
nextjs_space/app/api/shop/register/route.ts (2)
27-33: Consider validating nested required fields.The validation only checks for top-level objects but
personal.firstName,personal.lastName, andpersonal.emailare used directly in thecreateClientcall. Missing nested fields would cause unclear errors downstream.♻️ Suggested improvement
// Validate required fields - if (!personal || !address || !medicalRecord) { + if (!personal || !address || !medicalRecord) { + return NextResponse.json( + { error: "Missing required fields" }, + { status: 400 }, + ); + } + + if (!personal.firstName || !personal.lastName || !personal.email) { return NextResponse.json( - { error: "Missing required fields" }, + { error: "Missing required personal information" }, { status: 400 }, ); }
100-106: PersistdrGreenClientIdto the user record.The Dr. Green
clientIdis returned in the response but not stored in the database. The schema already has adrGreenClientIdfield (line 149 of schema.prisma); add it to the user update to avoid losing this identifier for future API calls or lookups.♻️ Suggested improvement
await prisma.users.update({ where: { id: dbUser.id }, data: { name: `${personal.firstName} ${personal.lastName}`, + drGreenClientId: result.clientId, }, });nextjs_space/scripts/test-drgreen-products.ts (1)
46-55: Avoid hardcoded dummy signature to keep test results meaningful.
Line 54 forces auth failure; consider generating a real signature (or omitting the header) when validating the integration path.nextjs_space/scripts/test-drgreen-auth.ts (1)
83-93: Align signature inclusion with production logic to avoid false negatives.
Line 92 always signs even for empty GET query strings;callDrGreenAPIonly signs when a payload exists (or non‑GET). Mirroring that behavior will make this test closer to prod.♻️ Suggested adjustment
- // Always sign? - headers['x-auth-signature'] = generateSignature(signaturePayload, secretKey); + // Mirror prod: sign only if there is a query string payload + if (signaturePayload) { + headers['x-auth-signature'] = generateSignature(signaturePayload, secretKey); + }
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@nextjs_space/app/api/shop/register/route.ts`:
- Around line 62-90: The current phone parsing silently defaults phoneCode to
"+27" and leaves contactNumber unnormalized, which fails for non-ZA tenants and
formatted inputs (e.g., "+1 (555) 123-4567"); update the logic around phone,
phoneCode, and contactNumber used when calling createClient to: strip all
non-digit/leading-plus characters from the input, validate/parse the result as
E.164 if it has a leading "+", extract the international calling code and
national number robustly (improving the current regex), and if there is no
leading "+" derive the calling code from tenant?.countryCode via a
tenant-to-dial-code mapping (or explicitly reject invalid/ambiguous values);
ensure you no longer default to "+27" silently and return/throw a validation
error for unknown/unparsable numbers so createClient always receives normalized
digits in phoneCode and contactNumber.
In `@nextjs_space/scripts/debug-kyc-auth.ts`:
- Around line 78-81: The script currently logs missing user or
user.drGreenClientId then returns which yields exit code 0; change the control
flow in the if-block that checks "if (!user || !user.drGreenClientId)" to call
process.exit(1) after logging the error (preserving the existing console.error
message and user payload) so the script exits with a failure status for CI/CD;
locate this check in debug-kyc-auth.ts and replace the lone "return" with
process.exit(1).
🧹 Nitpick comments (5)
nextjs_space/scripts/debug-kyc-auth.ts (5)
2-6: Unused import and inconsistent module style.
httpsmodule is imported but never used (the script usesfetchinstead on line 115).- Using CommonJS
require()in a TypeScript file is inconsistent with the type annotations present. Consider ES module imports for consistency.♻️ Suggested cleanup
-const cryptoModule = require("crypto"); -const { PrismaClient } = require("@prisma/client"); -const https = require("https"); // or fetch if node 18+ +import * as cryptoModule from "crypto"; +import { PrismaClient } from "@prisma/client";
21-24: Consider stronger typing for better debugging.The
anytype on line 21 defeats TypeScript's benefits. Also, silently returning the original text when the format doesn't match (line 24) could mask decryption issues during debugging.♻️ Suggested improvement
-function decryptText(text: any) { - if (!text) return ""; - const parts = text.split(":"); - if (parts.length !== 3) return text; +function decryptText(text: string | null | undefined): string { + if (!text) return ""; + const parts = text.split(":"); + if (parts.length !== 3) { + console.warn(`decryptText: unexpected format, returning as-is: ${text.substring(0, 20)}...`); + return text; + }
39-44: Silent error swallowing hides debugging information.The empty catch block silently ignores base64 decoding failures. In a debug script, this could mask the root cause of signature failures. Consider logging the error.
♻️ Suggested fix
try { privateKeyPEM = Buffer.from(secretKey, 'base64').toString('utf-8'); } catch (error) { - // ignore + console.warn("Failed to decode secretKey as base64, using as-is:", error); }
78-84: Consider limiting PII in debug output.Logging the entire
userobject (line 79) may expose PII if script output is captured in CI logs or shared for debugging. Consider logging only necessary identifiers.if (!user || !user.drGreenClientId) { - console.error("User or DrGreenClientId not found:", user); + console.error("User not found or missing drGreenClientId for email:", userEmail); return; }
117-124: Truncating error/response data may hide debugging details.For a debugging script, truncating error messages (line 119) and response data (line 123) could hide the information needed to diagnose issues. Consider logging full content or using a larger limit.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.