Skip to content

30 28 23 8 4 2 furqan bodarni - #342

Open
Furqan-7 wants to merge 6 commits into
modelsuite-ai:masterfrom
Furqan-7:30-28-23-8-4-2-furqan-bodarni
Open

30 28 23 8 4 2 furqan bodarni#342
Furqan-7 wants to merge 6 commits into
modelsuite-ai:masterfrom
Furqan-7:30-28-23-8-4-2-furqan-bodarni

Conversation

@Furqan-7

Copy link
Copy Markdown

Summary

Fixes all 6 assigned issues. Also fixes a pre-existing broken ESLint config
that was failing Client Lint on a clean checkout — left as the first commit
since it's a prerequisite for CI to pass at all.

Changes

#4 — Unrestricted File Uploads
Added a multer fileFilter that checks both MIME type and file extension
against an allowlist (PDF + common image formats), so executables can't be
uploaded — including renamed ones. Added error-handling middleware so a
rejected upload returns a clean 400 JSON response instead of crashing.

#2 — Admin-to-Admin Assignment
Backend now validates that assignedTo refers to a Talent-role user on both
create and update, rejecting with 400 otherwise. The UI dropdown already
filtered to Talents only — this closes the API-level gap.

#8 — Approval Doesn't Cascade to Parent Task
Approving a submission now sets the parent task's status to a new
Completed state, wired through every place task status is shown (table,
task list, filters, dashboard stats).

#23 — Simulated Assignment Notifications
Added a notifyAssignment helper that fires when a task is assigned to a
talent — on create, and on update only when the assignee actually changes.

#28 — Missing Active Nav Highlighting
Root cause: two sidebar links ("Talents", "My Tasks") pointed at routes
that were never registered, so they 404'd and could never show an active
state. Registered both — "My Tasks" reuses the existing dashboard route
pattern, "Talents" is a new page built on an API call that already existed
elsewhere in the app.

#30 — Password Visibility Toggle
Added a reusable PasswordInput component with a show/hide eye icon, used
on both Login and Register.

Issues

Closes #4, Closes #2, Closes #8, Closes #23, Closes #28, Closes #30

Testing

  • npm run lint — clean, 0 warnings/errors (client + server)
  • npm run build — clean
  • Manually tested each fix locally

Video

https://drive.google.com/file/d/1wHgbt_ode6PG4G8e7yRcQ_ma_x3P-MmN/view?usp=sharing

Furqan Bodarni added 6 commits July 18, 2026 18:43
- client eslint.config.js had eslint-plugin-react in devDependencies but
  never wired into the flat config, so plain no-unused-vars couldn't see
  that imported components are used via JSX and flagged every single
  component import as unused (40 errors). Added just the jsx-uses-vars
  rule (not the full recommended set, which would pull in
  react-in-jsx-scope/prop-types that don't apply to this React 19 /
  PropTypes-free codebase).
- package-lock.json was out of sync with package.json (locked eslint
  10.x / missing eslint-plugin-react's tree entirely while package.json
  declared eslint ^9.7.0 + eslint-plugin-react). Regenerated via
  npm install so npm ci works cleanly again.
- AuthContext.jsx exported both a component (AuthProvider) and a hook
  (useAuth) from one file, which react-refresh/only-export-components
  flags. Split into authContextInstance.js (the context object),
  AuthContext.jsx (just the AuthProvider component), and useAuth.js
  (the hook) — updated all six importers accordingly.
- server/middleware/authMiddleware.js had an unused catch binding
  (no-unused-vars); now logs it instead of swallowing it silently.

None of this touches application behavior — it's what's needed for
'npm run lint --max-warnings 0' to pass at all on either package,
which the assigned issues below build on top of.
…bles

middleware/upload.js accepted any file type with no validation — an
attacker (or an unwitting talent) could upload a .exe, .sh, etc. through
the submission upload endpoint.

Added a multer fileFilter that only accepts PDF and common image types,
checked by BOTH MIME type and extension so a renamed file (malware.exe
renamed to .pdf) or a spoofed Content-Type can't slip through on either
check alone.

Also added an Express error-handling middleware in index.js, since a
fileFilter rejection surfaces as a thrown error that previously had
nowhere to go but Express's default HTML error page — now it returns a
clean 400 JSON response instead.
…t + notify talents on assignment

Grouped together since both live in the same createTask/updateTask
assignment code path.

modelsuite-ai#2 — Task Assignment Allows Admin-to-Admin Assignment:
The frontend's assign-to dropdown only ever lists Talent users (via
fetchTalents), but the backend never validated assignedTo at all, so a
direct API call (Postman, curl, etc.) could assign a task to an Admin.
Added validateAssignee(), called from both createTask and updateTask,
which 404s on a missing user and 400s if the user's role isn't 'Talent'.

modelsuite-ai#23 — Simulate Assignment Notifications:
Talents had no way of knowing a task had been assigned to them. Added
utils/notifications.js with a notifyAssignment() helper (console-logs a
clearly structured notification, easy to swap for real email/push later)
and call it from createTask when a task is created with an assignee, and
from updateTask only when assignedTo actually changes to a new value
(compares against the previous assignee so edits that don't touch
assignment, or reassign to the same person, don't spam duplicate
notifications).
…n is approved

reviewSubmission updated Submission.reviewStatus but never touched the
parent Task — approving a submission left the task stuck on 'Submitted'
forever, with no way to tell completed work apart from work still
awaiting review.

- Added 'Completed' to Task's status enum, and a matching status-badge
  CSS class + STATUS_CLASS entries everywhere a task's status renders
  (TasksTable, MyTasksList, TaskCard) plus the admin status filter/edit
  dropdowns.
- reviewSubmission now sets the parent Task's status to 'Completed' when
  reviewStatus is 'Approved'. (A rejection intentionally leaves the task
  as 'Submitted' — that already lets the talent resubmit, so no separate
  cascade is needed there.)
- Constrained Submission.reviewStatus to an enum of
  ['Pending','Approved','Rejected'] — it previously accepted any string,
  which the cascade logic depends on being one of those three.
- The Admin dashboard's 'Approved' stat card would now always read 0
  (tasks move straight to Completed on approval instead), so swapped it
  to track Completed instead — otherwise the fix here would've quietly
  broken that card.
Both sidebars had nav items pointing at routes that were never
registered in App.jsx:
- Admin Sidebar's 'Talents' -> /admin/talents (backend endpoint
  GET /api/users/talents already existed and is already used by the
  assign-to dropdown, but no page consumed it directly)
- Talent Sidebar's 'My Tasks' -> /talent/tasks (TalentDashboard was only
  mounted at /talent/dashboard)

Clicking either nav item hit the catch-all NotFoundPage, which doesn't
render the sidebar at all — so those items could never show an active
state, since the route highlighting logic (location.pathname === path)
had nothing valid to ever match against.

- Added /talent/tasks as a second route onto TalentDashboard, mirroring
  the existing /admin/dashboard + /admin/tasks pattern that already maps
  two routes onto AdminDashboard.
- Added a new TalentsPage (admin-only) listing all Talent-role users via
  the existing fetchTalents() API call, and registered it at
  /admin/talents, styled to match the existing SubmissionsPage table.
Login and Register both had plain type=password inputs with no way to
check what you'd typed before submitting.

Added a reusable PasswordInput component (components/common/) with an
eye-icon button that toggles the input between type='password' and
type='text'. Matches the existing input styling exactly (takes the same
inputCls used elsewhere) and is keyboard/screen-reader friendly
(type='button' so it can't submit the form, aria-label/aria-pressed
reflect the current state, tabIndex={-1} so it doesn't interrupt the
email->password->submit tab order). Used it in both LoginPage and
RegisterPage in place of the raw <input type="password">.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment