Bug #11426
openWorking Issues CUST-096 To CUST-154
Description
Single Enter adds the email/phone entry twice. "1. Go to appforsolar.com/customers
2. Create Customer
3. Type qatestclaude@yopmail.com in Email and press Enter once" One chip added; payload has one value. 2 identical chips; payload email:[x, x], phone:[x, x]. 5 of 20 existing customers already have duplicated phones in DB. Fix Enter key handling so one entry is added once; dedupe values before save; clean existing duplicates.
Uppercase letters in email are deleted instead of lowercased. "1. Go to appforsolar.com/customers
2. Create Customer
3. Type QA.Tester1@YopMail.com / ABC@GMAIL.COM" Email converted to lowercase (qa.tester1@yopmail.com). QA.Tester1@YopMail.com becomes .ester1@opail.com; ABC@GMAIL.COM becomes empty. Lowercase the value instead of stripping capitals.
Auth token readable by JavaScript and no Content-Security-Policy. "1. Go to appforsolar.com/customers
2. Run document.cookie in console" authtoken cookie is HttpOnly; CSP header present. document.cookie exposes authtoken, company and user cookies; no CSP – any XSS could steal sessions. Set authtoken HttpOnly, Secure, SameSite and add a CSP.
UI stuck forever when a request fails. "1. Go to appforsolar.com/customers
2. Create/Delete a customer while the request fails (simulated by blocking the request)" Error message or timeout; user can retry/cancel. Create: 'Submitting, please wait...' overlay never ends. Delete: spinner forever, Cancel/X/Esc disabled. Email: 'Checking email...' keeps field disabled. Page reload needed. Handle request errors/timeouts: hide loaders, show an error and re-enable controls.
Hidden default values saved for fields left as 'Select an option'. "1. Go to appforsolar.com/customers
2. Create Customer leaving Source / Installation Type empty
3. Check save payload" Empty fields saved as empty / required to pick. Payload sends leadSource:'other', installationType:'roof', homeOwnerShip:'other'. 15 of 20 customers have Source = Others. Do not send default values the user didn't pick; make them required if needed.
Only current page number shown in pagination. "1. Go to appforsolar.com/customers
2. Set 5 rows per page with 20 records" Page list/total shown so user can jump pages. API returns totalpages:4 but UI shows only '1'. Customers API uses 'totalpages', users API uses 'totalPages' – likely key mismatch. Fix the key mismatch and show page numbers / total.
Customer with no type disappears from Residential/Commercial views. "1. Go to appforsolar.com/customers
2. Compare Residential and Commercial counts with total" Every record appears in a view; counts add up. Residential 11 + Commercial 8 = 19, not 20; Customers card 5 + 1 = 6, not 7 (Address Type is optional so customerType is empty). Make Address/Customer Type required or add an 'Unspecified' bucket.
Duplicate customers allowed. "1. Go to appforsolar.com/customers
2. Create a customer with an existing email/phone" Duplicate blocked or requires strong confirmation. Only a warning with 'Yes, Add Anyway'. (855) 582-9175 used by 'nishitha MC' and 'Nishi M'; 9876543210 by 'Test Customer' and 'kodali ramu'. Block duplicates or merge; review existing duplicate records.
Different times shown for the same record (UTC vs IST). "1. Go to appforsolar.com/customers
2. Check Created On for 'testing test' in list
3. Open View History" Same time zone everywhere. List shows 10/06/2026 11:42 AM (UTC); history shows 'Create ... 05:12 PM' (IST). Show all times in the user's time zone.
Valid name characters stripped (spaces, apostrophes, hyphens). "1. Go to appforsolar.com/customers
2. Enter name ""John O'Neil-Smith Jr""" Name saved as typed. Becomes "JohnONeilSmithJr". Allow spaces, apostrophes and hyphens in names.
Roof age has no upper limit. "1. Go to appforsolar.com/customers
2. Enter Roof age 999" Unrealistic values rejected. Roof age 999 accepted. Add a realistic max (e.g. 0–100 years).
Name sort is case-sensitive; Status sorts by internal value. "1. Go to appforsolar.com/customers
2. Sort by Name ascending
3. Sort by Status" Case-insensitive name sort; status sorted by label. Name: Ashok ... Thirupathi, then jerry, john, kodali. Status order: Prospect, Not Interested, Lead, Interested, Customer. Use case-insensitive sort and sort Status by label.
Source options in list filter and Create form don't match. "1. Go to appforsolar.com/customers
2. Compare Source filter options with Create Customer Source options" Same options and labels in both. 'Website' exists in the filter but not in the form (always returns 0); 'Internals' vs 'Internal', 'Event' vs 'Events'. Use one shared Source list for filter and form.
Role filter options with no users. "1. Go to appforsolar.com/customers
2. Open Assigned Manager/To role filters" Only roles with users shown, or users listed. Manager, Sales, Finance, Engineer, Installer, Executive → 0 users. Hide empty roles or fix role mapping.
Mixed phone formats stored. "1. Go to appforsolar.com/customers
2. Review phone numbers in list" One consistent format. '9876543210' vs '(987) 654-3210'. Normalise phone format on save and migrate existing data.
Status and workflow stage disagree. "1. Go to appforsolar.com/customers
2. Compare Status with workflow stage" Status and stage consistent. Examples: converted/lead, interested/assessment. Confirm with developers whether this is expected; align status with stage.
Address validation errors in console – address validation never runs. "1. Go to appforsolar.com/customers
2. Open Create Customer and fill address
3. Check console" Address fields validated. 57 console exceptions: 'schema does not contain the path addresses0.street1 / zipcode / addressType'. Add address paths to the validation schema.
Client controls collection/fields in existence check. "1. Go to appforsolar.com/customers
2. Type an email in Create Customer
3. Inspect checkRecordExistence request" Server decides collection and fields. Body: {collection:'Customer', value:..., fields:['email']} sent from browser – could probe other tables (e.g. users). Whitelist collection/fields server-side.
Missing security headers and version disclosure. "1. Go to appforsolar.com/customers
2. Inspect response headers" CSP, X-Frame-Options, HSTS, X-Content-Type-Options, Referrer-Policy present; no versions. None present; Server: nginx/1.24.0 (Ubuntu); X-Powered-By: Express. Add headers and hide version info.
Duplicate API calls. "1. Go to appforsolar.com/customers
2. Change 'Show' filter / open Deleted view / type email
3. Watch Network tab" Each request fires once. getCustomers fires twice on 'Show' change and in Deleted view; checkRecordExistence twice per email. Remove duplicate calls.
Wrong status code for missing token (403 instead of 401). 1. GET /api/customers/getCustomerStats without token 401 Unauthorized. 403 'No token provided'. Return 401 when no token is sent.
Excess data in customer list response. "1. Go to appforsolar.com/customers
2. Inspect getCustomers response" Only fields the table needs. assignedManager/assignedTo objects include managers' email, phone and role. Return only required fields.
Leading space in API messages. "1. Go to appforsolar.com/customers
2. Inspect API response messages" No stray spaces. " Customers fetched successfully". Trim message strings.
Public config exposes environments and Mapbox token. 1. Open app-config.js Only current env values; restricted token. Lists dev/stage/prod API URLs and a Mapbox token. Ship only current env; restrict the Mapbox token to your domain.
Wrong placeholder wording. "1. Go to appforsolar.com/customers
2. Open Create Customer" Placeholders refer to the customer. 'Enter Your First Name'. Change to 'Enter first name' etc.
Inconsistent error capitalisation. "1. Go to appforsolar.com/customers
2. Submit empty Create Customer form" Consistent capitalisation. 'First Name is Required' vs 'At least one email is required'. Use one capitalisation style.
Misleading required marker on Annual Power Consumption. "1. Go to appforsolar.com/customers
2. Open Create Customer" * only on truly required fields. Annual Power Consumption marked * but optional if a bill is uploaded. Show conditional requirement clearly.
Delete dialog shows no customer name; reason not trimmed. "1. Go to appforsolar.com/customers
2. Click Delete on a customer
3. Enter reason with leading spaces" Dialog names the customer; reason trimmed. Dialog does not say who is being deleted; leading spaces count toward 100 chars and are saved. Show customer name; trim reason.
Grammar in View History. "1. Go to appforsolar.com/customers
2. Open View History" 'Created by ...'. 'Create by ...'. Fix text.
Inconsistent filter apply behaviour, no Clear button. "1. Go to appforsolar.com/customers
2. Change Show, Source and Status filters" Consistent apply behaviour and a reset option. 'Show' applies instantly; Source/Status need Filter button; no reset/clear button. Make apply consistent and add Clear.
Disabled follow-up button gives no reason. "1. Go to appforsolar.com/customers
2. Open the three-dot menu on a customer with follow-up" Tooltip explaining why disabled. 'Follow-up already assigned' button looks disabled with no visible tooltip. Add a visible tooltip/reason.
Missing alt text and labels. "1. Go to appforsolar.com/customers
2. Inspect images and filter dropdowns" Images have alt; dropdowns have labels. 15 images without alt text; filter dropdowns have no labels. Add alt text and labels.
Address suggestions overlap header and Save button. "1. Go to appforsolar.com/customers
2. Type a street in Create Customer" Suggestion list doesn't cover header/Save. Street suggestion list covers the sticky header and Save button. Fix z-index/positioning of the suggestion list.
Different default rows per page in Deleted view. "1. Go to appforsolar.com/customers
2. Open Deleted Customers view" Same default (10 rows) as main list. Loads with 5 rows/page. Use the same default.
Completed Visits counts only a sample/demo record. "1. Go to appforsolar.com/site-visits
2. Check Completed Visits card and list" Only real visits counted (real completed = 0). Only completed visit is SV-SAMPLE-PATEL: customer '—', no phone, no manager, purpose '[SAMPLE PIPELINE] Pre-survey visit completed'. Remove seed data from the environment, or exclude it from stats.
Cards do not cover all statuses. "1. Go to appforsolar.com/site-visits
2. Compare cards with Status filter options" Cards for all statuses (or a total), or clarify which statuses each card includes. Filter has Pending, Scheduled, In Progress, Completed, Not Required, Cancelled; cards show only Open, Scheduled, Completed. Add In Progress (and a total), or state which statuses each card includes.
"Save Schedule Only" saves an empty schedule. "1. Go to appforsolar.com/site-visits
2. Open SV-003
3. Leave Scheduled Visit Date & Time and Visit Notes empty
4. Click Save Schedule Only" Save blocked with "Scheduled date and time is required". Toast "Visit schedule saved"; PUT /api/site-visits/{id} returns 200. Require date/time before saving, on screen and API.
Shaded Area can be larger than Gross Area. "1. Go to appforsolar.com/site-visits
2. Open a visit
3. Roof Length 50, Width 10 (Gross 500 sq ft), Shaded Area 900" Error: "Shaded area cannot exceed gross area". No error; Usable Area silently becomes 0. Add cross-field validation.
Roof Pitch accepts values above its own limit. "1. Go to appforsolar.com/site-visits
2. Open a visit
3. Enter Roof Pitch 95" Error for values outside 0–60°. Field has max=60 but 95 is accepted with no error. Enforce the 0–60° range with an error message.
Roof Age accepts negative values. "1. Go to appforsolar.com/site-visits
2. Open a visit
3. Enter Roof Age 5" Values below 0 rejected with a clear message. Accepted; field still shows "Required — not yet collected" instead of an invalid-value message. Reject negatives with a clear message.' / '003'. Fit table to width, prevent ID wrapping, add Actions header or remove empty column.
Scheduled Visit Date allows past dates. "1. Go to appforsolar.com/site-visits
2. Open a visit
3. Pick a past date in Scheduled Visit Date & Time" Only today or later allowed. Date-time input has no min value; past dates can be picked. Set min date and show an error for past dates.
Roof Height has no upper limit. "1. Go to appforsolar.com/site-visits
2. Open a visit
3. Enter Roof Height 1212, then try a minus sign" Realistic range (e.g. 1–200 ft) with error message. 1212 ft accepted; minus sign silently dropped. Add range validation with an error.
Monthly Consumption flagged missing although Annual is filled. "1. Go to appforsolar.com/site-visits
2. Open SV-003 (Annual Consumption 1528 kWh)" Monthly auto-filled (annual ÷ 12) or either field satisfies requirement. Monthly Consumption marked "Required — not yet collected". Auto-fill monthly or accept either field.
Visit Notes has no length limit. "1. Go to appforsolar.com/site-visits
2. Open a visit
3. Inspect Visit Notes textarea" Defined limit with counter, enforced by API. No maxlength or counter. Add a limit and counter; enforce on API.
Sample record shown in the live list. "1. Go to appforsolar.com/site-visits
2. Look at SV-SAMPLE-PATEL row" Seed data hidden; 'Customer deleted' shown if customer missing. Shows '?' avatar, '—' customer and empty manager. Hide or delete seed data.
Table overflows and last column has no header. "1. Go to appforsolar.com/site-visits
2. View list at 1117 px width" Table fits; IDs don't wrap; Actions header present. Created Date cut off with horizontal scrollbar; last column (row arrow) has no header; Visit IDs wrap as 'SV
Different font and button style on the detail page. "1. Go to appforsolar.com/site-visits
2. Open a visit" One font and button style across the app. List uses Poppins; visit form uses a different font and ALL-CAPS buttons. Use one font and button style.
Inconsistent date formats. "1. Go to appforsolar.com/site-visits
2. Compare dates on list, detail and date input" One date format everywhere. List 'Oct 6, 2026'; detail '7/3/2026, 10:00:00 AM'; input 'dd-mm-yyyy'. Use one date format.
Completed visit still says details are missing. "1. Go to appforsolar.com/site-visits
2. Open SV-SAMPLE-PATEL" Subtitle changes when visit is completed. Subtitle reads "Collect missing site assessment details from the customer" next to a Completed badge. Update subtitle based on status.
Scheduled date earlier than created date. "1. Go to appforsolar.com/site-visits
2. Open SV-SAMPLE-PATEL" Scheduled date cannot be before creation. Created Aug 18, 2026, scheduled 7/3/2026. Validate scheduled date ≥ created date.
Detail page opens scrolled down. "1. Go to appforsolar.com/site-visits
2. Scroll the list and open a visit" Detail page opens at the top. Scroll position is kept; page header partly hidden. Scroll to top on navigation.
Notification badge count changes between pages. "1. Go to appforsolar.com/site-visits
2. Move between list and detail" Stable count that changes only on new/read notifications. Badge showed 59, then 58, then 59 with no new activity. Fix notification count source.
API accepts an empty schedule update. 1. PUT /api/site-visits/{id} with empty date and notes (see V-1) 400 with field errors. Returned 200. Add server-side validation for required fields and number ranges.
List is capped at 100 records with no pagination. "1. Go to appforsolar.com/site-visits
2. Check Network: GET /api/site-visits?limit=100" Server-side pagination, search and filter. Called once with limit=100; search and status filter run in the browser only – visits beyond 100 won't appear. Implement server-side pagination, search and filter.
Session token cookie is not HttpOnly (app-wide). "1. Go to appforsolar.com/site-visits
2. Run document.cookie in console" auth_token is HttpOnly, Secure, SameSite. document.cookie lists auth_token. Set auth_token as HttpOnly, Secure, SameSite.
Check privacy of uploaded electricity bills. "1. Go to appforsolar.com/site-visits
2. Upload a bill on the visit form
3. Check storage URL" Bills stored privately with short-lived signed URLs. Company logo is served from a public S3 bucket (regalimagesprod); bills may use the same bucket. Verify and store bills privately with signed URLs.
Database IDs exposed in URLs. "1. Go to appforsolar.com/site-visits
2. Open a visit and check URL" API checks visit belongs to user's company. URL is /site-visits/6ac4e6a2ae33ac429662ac7e (raw object ID). Confirm ownership checks (IDOR test still to do).
Subtasks
Related issues