Skip to content

feat: add sponsors program support#4342

Open
Utkarsh-0304 wants to merge 11 commits intoOWASP:mainfrom
Utkarsh-0304:feat/sponsors
Open

feat: add sponsors program support#4342
Utkarsh-0304 wants to merge 11 commits intoOWASP:mainfrom
Utkarsh-0304:feat/sponsors

Conversation

@Utkarsh-0304
Copy link
Copy Markdown
Contributor

@Utkarsh-0304 Utkarsh-0304 commented Mar 23, 2026

Proposed change

Resolves #4259

This PR adds support for the sponsors program within the Nest Platform.

  • It extends the existing Sponsor model with status, message, contact_email, created_at and optional entity fields. Also, status can now be handled through the Django Admin Panel.

  • It also adds a /sponsors route to display active sponsors, ordered by sponsor level, in a clear and professional manner.

  • Another route, /sponsors/apply, allows organisations to apply for sponsorship by filling out a form with their organisation name, contact email, website URL, and a message indicating their interest in sponsoring.

  • Additionally, it adds a REST Endpoint for exposing active sponsor information for external integrations.

demo.webm
fill_sponsorship_form.webm

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds sponsor support across backend and frontend: model and migration additions, GraphQL nodes/queries/mutation and schema wiring, a REST endpoint exposing active sponsors, admin fieldset update, frontend sponsor list and application pages/components, GraphQL documents, and local Docker Compose volume renames.

Changes

Cohort / File(s) Summary
Sponsor Model & Migrations
backend/apps/owasp/models/sponsor.py, backend/apps/owasp/migrations/0073_sponsor_contact_email_sponsor_created_at.py, backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py, backend/apps/owasp/migrations/0075_sponsor_message.py
Adds Sponsor.StatusType (DRAFT, ACTIVE, ARCHIVED) and fields: created_at, contact_email, message, status, plus nullable chapter and project FKs; three migrations add these fields.
GraphQL — Nodes / Queries / Mutations / Schema
backend/apps/owasp/api/internal/nodes/sponsor.py, backend/apps/owasp/api/internal/queries/sponsor.py, backend/apps/owasp/api/internal/mutations/sponsor.py, backend/apps/owasp/api/internal/mutations/__init__.py, backend/settings/graphql.py
SponsorNode exposes description; adds CreateSponsorInput; introduces active_sponsors query (filters ACTIVE, orders by type); adds SponsorMutation.create_sponsor (creates Sponsor, slugifies key, handles IntegrityError) and re-exports it; wires SponsorMutation into global Mutation.
REST API
backend/apps/api/rest/v0/project.py
Adds REST endpoint GET /nest/sponsors (operation_id="list_nest_sponsors") returning active sponsors; decorated with cache_response.
Admin
backend/apps/owasp/admin/sponsor.py
Includes status in the Sponsor admin fieldsets under Status.
Frontend — Pages & Components
frontend/src/app/sponsors/page.tsx, frontend/src/app/sponsors/apply/page.tsx, frontend/src/components/SponsorForm.tsx
Adds sponsors listing page (queries activeSponsors, renders sponsor grid and CTA), sponsor application page (uses SponsorForm, executes create mutation, shows toast and navigates on success), and SponsorForm component with state, validation, and submit handling.
Frontend — GraphQL Documents
frontend/src/server/queries/sponsorQueries.ts, frontend/src/server/mutations/sponsorMutations.ts
Adds GET_SPONSORS query (requests activeSponsors fields: id, name, url, description, imageUrl) and CREATE_SPONSOR mutation (CreateSponsor(input) { id name url }).
Form Validation Utilities
frontend/src/components/forms/shared/formValidationUtils.ts
Adds validator usage and new exported validators: validateWebsite, validateContactEmail, and validateMessage.
Mutations Package Init
backend/apps/owasp/api/internal/mutations/__init__.py
Creates package init that re-exports SponsorMutation.
Infrastructure (local dev)
docker-compose/local/compose.yaml
Renames local Docker Compose named volumes by appending _4259 and updates service volume mounts accordingly.
Frontend package deps
frontend/package.json
Adds runtime dependency validator and dev type @types/validator.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes one out-of-scope change: docker-compose volume renaming (_4259 suffix) unrelated to the sponsors program implementation. All other changes directly support the core objectives. Remove docker-compose volume renaming changes or move them to a separate PR focused on Docker Compose configuration updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add sponsors program support' accurately and concisely describes the main change—adding sponsorship program functionality including model extensions, public routes, application form, and REST endpoint.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #4259: extended Sponsor model with status/message/contact_email/created_at fields and optional entity associations, public /sponsors route displaying active sponsors ordered by level, /sponsors/apply form creating draft records, and REST endpoint /nest/sponsors for external integrations.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description clearly describes the sponsorship program feature additions including model extensions, new routes (/sponsors and /sponsors/apply), and REST endpoint, aligning well with the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 19 files

Confidence score: 2/5

  • There are multiple high-confidence, user-impacting issues (sev 7–8) across infrastructure and API behavior, so merge risk is currently high.
  • Most severe: docker-compose/local/compose.yaml has suffixed service volume names (_4259) without matching top-level volume declarations, which can trigger undefined volume reference errors and break local Docker Compose startup.
  • In backend/apps/owasp/api/internal/mutations/sponsor.py, slug-based key generation can collide against a unique=True field and lead to unhandled integrity failures during sponsor creation; backend/apps/api/rest/v0/project.py also returns globally active sponsors instead of Nest-scoped results, causing incorrect API output.
  • Pay close attention to docker-compose/local/compose.yaml, backend/apps/owasp/api/internal/mutations/sponsor.py, backend/apps/api/rest/v0/project.py - fix volume declaration mismatches, enforce unique key handling, and scope /nest/sponsors to the Nest project.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/apps/owasp/api/internal/mutations/sponsor.py">

<violation number="1" location="backend/apps/owasp/api/internal/mutations/sponsor.py:22">
P1: Potential key collision from slugified sponsor name can cause unhandled DB integrity failures. The `key` field in `Sponsor` model is defined as `unique=True`. When creating a sponsor via `create_sponsor` mutation, if two sponsors share names that slugify to the same value (e.g., "ACME Corp" and "acme corp" both become "acme-corp"), an unhandled `IntegrityError` will be raised, causing a server error instead of a controlled GraphQL validation response. Consider adding collision handling logic similar to the existing `update_data` pattern in the Sponsor model.</violation>
</file>

<file name="backend/apps/api/rest/v0/project.py">

<violation number="1" location="backend/apps/api/rest/v0/project.py:164">
P1: The `/nest/sponsors` endpoint filters only by active status without scoping to the Nest project. The description claims to return "sponsors associated with project nest" but returns all active sponsors globally.</violation>
</file>

<file name="docker-compose/local/compose.yaml">

<violation number="1" location="docker-compose/local/compose.yaml:34">
P1: Service volume names were suffixed with `_4259` but top-level volume declarations were not updated, causing undefined volume reference errors in Docker Compose</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (9)
backend/apps/owasp/api/internal/queries/sponsor.py (1)

14-42: Extract duplicated sorting logic to reduce maintenance burden.

The sponsor type priority dictionary is duplicated between sponsors and active_sponsors resolvers. Consider extracting it to a module-level constant or helper function.

♻️ Proposed refactor
+SPONSOR_TYPE_PRIORITY = {
+    Sponsor.SponsorType.DIAMOND: 1,
+    Sponsor.SponsorType.PLATINUM: 2,
+    Sponsor.SponsorType.GOLD: 3,
+    Sponsor.SponsorType.SILVER: 4,
+    Sponsor.SponsorType.SUPPORTER: 5,
+    Sponsor.SponsorType.NOT_SPONSOR: 6,
+}
+
+
 `@strawberry.type`
 class SponsorQuery:
     """Sponsor queries."""

     `@strawberry_django.field`
     def sponsors(self) -> list[SponsorNode]:
         """Resolve sponsors."""
         return sorted(
             Sponsor.objects.all(),
-            key=lambda x: {
-                Sponsor.SponsorType.DIAMOND: 1,
-                Sponsor.SponsorType.PLATINUM: 2,
-                Sponsor.SponsorType.GOLD: 3,
-                Sponsor.SponsorType.SILVER: 4,
-                Sponsor.SponsorType.SUPPORTER: 5,
-                Sponsor.SponsorType.NOT_SPONSOR: 6,
-            }[x.sponsor_type],
+            key=lambda x: SPONSOR_TYPE_PRIORITY[x.sponsor_type],
         )

     `@strawberry_django.field`
     def active_sponsors(self) -> list[SponsorNode]:
         """Resolve active sponsors ordered by Sponsor level."""
         return sorted(
             Sponsor.objects.filter(status=Sponsor.StatusType.ACTIVE),
-            key=lambda x: {
-                Sponsor.SponsorType.DIAMOND: 1,
-                Sponsor.SponsorType.PLATINUM: 2,
-                Sponsor.SponsorType.GOLD: 3,
-                Sponsor.SponsorType.SILVER: 4,
-                Sponsor.SponsorType.SUPPORTER: 5,
-                Sponsor.SponsorType.NOT_SPONSOR: 6,
-            }[x.sponsor_type],
+            key=lambda x: SPONSOR_TYPE_PRIORITY[x.sponsor_type],
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/owasp/api/internal/queries/sponsor.py` around lines 14 - 42, The
duplicated sponsor-type priority mapping used in the sponsors and
active_sponsors resolvers should be extracted to a single reusable symbol:
create a module-level constant (e.g., SPONSOR_PRIORITY) or a small helper
function (e.g., get_sponsor_priority) that maps Sponsor.SponsorType values to
priority integers, then update sponsors() and active_sponsors() to use that
constant/helper in their key functions (replace the inline dict and lambda logic
with key=lambda x: SPONSOR_PRIORITY[x.sponsor_type] or
key=get_sponsor_priority), ensuring both resolvers share the same source of
truth.
backend/apps/owasp/admin/sponsor.py (1)

13-29: Consider adding status to list_display and list_filter.

Since status (Draft/Active/Archived) is a key field for sponsor workflow management, adding it to both list_display and list_filter would improve the admin experience for filtering and quickly identifying sponsors by their approval state.

♻️ Proposed enhancement
 list_display = (
     "name",
     "sort_name",
     "sponsor_type",
     "is_member",
     "member_type",
+    "status",
 )
 search_fields = (
     "name",
     "sort_name",
     "description",
 )
 list_filter = (
     "sponsor_type",
     "is_member",
     "member_type",
+    "status",
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/owasp/admin/sponsor.py` around lines 13 - 29, Add the "status"
field to the admin configuration so admins can view and filter by
Draft/Active/Archived; update the ModelAdmin's list_display tuple to include
"status" (alongside "name", "sort_name", etc.) and add "status" to the
list_filter tuple so it appears in filters — locate and edit the admin class
that defines list_display and list_filter (e.g., SponsorAdmin) and insert
"status" into both tuples.
frontend/src/app/sponsors/apply/page.tsx (1)

1-12: Sponsor application is publicly accessible without authentication—verify if intentional.

The sponsor mutation and frontend page lack authentication checks (no useSession(), no permission validation), unlike program creation which requires authentication and project leader privileges. If allowing external organizations to apply is intentional, implement rate limiting or CAPTCHA to prevent spam. If authentication is required, add a session check consistent with other mutation pages.

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

In `@frontend/src/app/sponsors/apply/page.tsx` around lines 1 - 12, The sponsor
application page CreateProgramPage exposes the CreateSponsor mutation via
createSponsor and SponsorForm without any authentication or anti-abuse controls;
either enforce the same session/permission checks used elsewhere (e.g., call
useSession()/getServerSession and verify project leader role before rendering or
invoking createSponsor) or, if public submissions are intended, add anti-abuse
measures (rate limiting or CAPTCHA integration around SponsorForm submissions
and server-side throttling) so anonymous requests cannot spam the mutation;
update the rendering logic and mutation handler to gate calls accordingly and
reuse existing auth helpers used by other pages to keep behavior consistent.
backend/apps/owasp/api/internal/mutations/sponsor.py (2)

19-19: Remove blank line after docstring.

Per static analysis (D202), remove the blank line after the function docstring.

✨ Proposed fix
     `@strawberry.mutation`
     def create_sponsor(self, input_data: CreateSponsorInput) -> SponsorNode:
         """Create a new sponsor."""
-
         sponsor = Sponsor.objects.create(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/owasp/api/internal/mutations/sponsor.py` at line 19, The
function docstring """Create a new sponsor.""" in the sponsor module has a blank
line immediately after it; remove that empty line so the docstring is followed
directly by the function body (i.e., edit the create_sponsor function in
sponsor.py to eliminate the blank line after the docstring to satisfy D202).

31-31: Consider including the sponsor key or name in the log message.

The current log message lacks context to identify which sponsor was created. Including the name would aid debugging and auditing.

✨ Proposed fix
-        logger.info("Sponsor created successfully")
+        logger.info("Sponsor '%s' created successfully", sponsor.name)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/owasp/api/internal/mutations/sponsor.py` at line 31, The log
call logger.info("Sponsor created successfully") lacks context—update the
logging in the sponsor creation flow (the logger.info call in sponsor.py inside
the sponsor creation function) to include an identifier from the created sponsor
(e.g., sponsor.id, sponsor.key or sponsor.name/created_sponsor.name) so the
message becomes descriptive (e.g., "Sponsor created successfully:
<identifier>"). Ensure you reference the same variable name used in the function
that holds the created sponsor object.
frontend/src/app/sponsors/page.tsx (2)

79-85: Consider using Link instead of button with router.push for navigation.

Using a Link component for navigation to /sponsors/apply provides better semantics, accessibility (proper anchor behavior), and enables prefetching.

♻️ Proposed fix
+import Link from 'next/link'
 ...
-          <button
-            type="button"
-            className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
-            onClick={() => router.push('/sponsors/apply')}
-          >
+          <Link
+            href="/sponsors/apply"
+            className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
+          >
             Apply here for Sponsorship
-          </button>
+          </Link>

This also allows removing useRouter if not used elsewhere.

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

In `@frontend/src/app/sponsors/page.tsx` around lines 79 - 85, Replace the
navigation button that calls router.push('/sponsors/apply') with a Next.js Link
to '/sponsors/apply' to improve semantics, accessibility, and enable
prefetching; update the JSX by swapping the <button> using
onClick={...router.push...} for a <Link> that preserves the same className and
inner text ("Apply here for Sponsorship"), and remove the useRouter import/usage
from this page component if it is no longer referenced.

11-17: Consider using generated types instead of a local Sponsor type.

The local Sponsor type may drift from the GraphQL schema. Using the generated types from GetSponsorsDataDocument ensures type safety and consistency.

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

In `@frontend/src/app/sponsors/page.tsx` around lines 11 - 17, Replace the local
Sponsor type with the generated GraphQL type to avoid schema drift: remove the
local "type Sponsor" and import the appropriate generated type that corresponds
to GetSponsorsDataDocument (e.g., GetSponsorsData or GetSponsorsDataQuery) from
your GraphQL codegen output, then use that imported type wherever "Sponsor" was
referenced (for example in props, state, or fetch result handling) so the
component relies on the generated schema types instead of a manually defined
one.
frontend/src/components/SponsorForm.tsx (1)

173-186: Minor inconsistency: FormTextarea uses onChange while FormTextInput uses onValueChange.

This is a minor API inconsistency between the two form components. Consider aligning them if the underlying components support it, or document this difference.

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

In `@frontend/src/components/SponsorForm.tsx` around lines 173 - 186, The
FormTextarea usage is inconsistent with FormTextInput's API (Textarea uses
onChange while TextInput uses onValueChange); update the SponsorForm.tsx to use
the same prop name across both components — either switch the FormTextarea call
to onValueChange (calling handleInputChange('message', value) and
setTouched(...) with the new value parameter) or refactor FormTextInput to also
accept onChange so both components share the same handler name; locate
FormTextarea and FormTextInput usages and adjust the prop name and corresponding
handler (handleInputChange, setTouched, errors, touched) so the API is
consistent project-wide.
backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py (1)

14-23: Consider adding blank=True to ForeignKey fields for admin form compatibility.

The chapter and project ForeignKey fields are null=True but not blank=True. In Django Admin, this means the fields will appear required even though they can be null in the database. If these fields are intended to be optional in forms, add blank=True.

✨ Proposed model adjustment
-field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='owasp.chapter'),
+field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='owasp.chapter'),

Same for the project field.

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

In
`@backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py`
around lines 14 - 23, The ForeignKey additions for model_name='sponsor' (fields
name='chapter' and name='project' added via migrations.AddField) set null=True
but not blank=True, causing admin forms to treat them as required; update those
migration field definitions to include blank=True on both the chapter and
project ForeignKey declarations so forms treat them as optional (i.e., add
blank=True alongside null=True and on_delete=... in the migrations.AddField
entries for 'chapter' and 'project').
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/api/rest/v0/project.py`:
- Around line 160-164: The REST endpoint list_nest_sponsors currently returns
active sponsors unsorted; update it to apply the same sponsor-type ordering used
by the GraphQL active_sponsors resolver (Diamond → Platinum → Gold → Silver →
Supporter → Not Sponsor) so results are consistent. Modify the SponsorModel
query in list_nest_sponsors to order by sponsor type using the same priority
mapping (either replicate the Case/When ordering here or reference the shared
priority constant if one exists), e.g., convert the filter-only return to a
queryset with .order_by(...) that uses the priority mapping for
SponsorModel.StatusType values. Ensure the ordering logic references
SponsorModel and SponsorModel.StatusType so it stays tied to the model enum.

In `@backend/apps/owasp/api/internal/mutations/sponsor.py`:
- Around line 21-29: Sponsor creation can raise IntegrityError because
key=slugify(input_data.name) is unique; change the creation logic around
Sponsor.objects.create to ensure a unique key before saving (or catch
IntegrityError and retry). Build a candidate_key from slugify(input_data.name)
and if Sponsor.objects.filter(key=candidate_key).exists() (or on
IntegrityError), append a deterministic suffix (e.g., incrementing counter or
short uuid) and retry until unique, then call Sponsor.objects.create(...,
key=candidate_key, ...); reference Sponsor.objects.create and
slugify(input_data.name) when making the change.

In `@backend/apps/owasp/models/sponsor.py`:
- Around line 76-81: The ForeignKey fields chapter and project on the Sponsor
model (status = ..., then chapter = models.ForeignKey("owasp.Chapter", ...) and
project = models.ForeignKey("owasp.Project", ...)) are nullable in the DB but
missing blank=True, causing ModelForm/Admin validation failures; update both
field definitions to include blank=True (e.g., chapter = models.ForeignKey(...,
null=True, blank=True) and project = models.ForeignKey(..., null=True,
blank=True)) and create/update the corresponding migration (0074) to reflect
this change so forms properly accept empty values.

In `@docker-compose/local/compose.yaml`:
- Line 34: Update the cleanup targets that remove Docker volumes to match the
renamed volumes in compose.yaml (e.g., backend-venv_4259 and the other volumes
suffixed with _4259 referenced in the compose file) so the Makefile targets
actually remove the new names; edit the cleanup targets in the project Makefiles
that currently reference the old volume names and replace them with the
corresponding _4259 names (or change the targets to use docker-compose down
--volumes / docker compose ... to drive cleanup from the compose file).
- Line 34: Service definitions reference suffixed named volumes like
backend-venv_4259, but the top-level volumes section still lists unsuffixed
names causing Compose to fail; update the top-level volumes declarations to
include the matching suffixed names (e.g., add entries for backend-venv_4259,
postgres_data_4259, redis_data_4259, etc.) or change the service mount names to
the unsuffixed identifiers so they match; ensure every volume referenced in
service mounts (lines showing backend-venv_4259 and the other indicated volumes)
has a corresponding declaration in the top-level volumes block (or vice‑versa)
so no referenced volume is undefined.

In `@frontend/src/app/sponsors/apply/page.tsx`:
- Line 10: The component is misnamed CreateProgramPage; rename the React
component function to SponsorApplicationPage (or SponsorApplyPage consistently)
and update the default export at the end of the file to export that new name so
the file reflects the sponsor application page; update any internal references
within this file (e.g., the component declaration const CreateProgramPage = ()
=> { and the export default CreateProgramPage) to use SponsorApplicationPage
instead.

In `@frontend/src/app/sponsors/page.tsx`:
- Around line 67-71: The component currently destructures only data and loading
from useQuery(GetSponsorsDataDocument) and returns an empty <div> while loading;
update the query usage to also destructure error (e.g., const { data:
sponsorsData, loading, error } = useQuery(GetSponsorsDataDocument)), render a
meaningful loading state (spinner/skeleton or accessible "Loading..." element)
when loading is true, and render an error UI when error is present (friendly
message and optional retry action) instead of silently showing nothing; keep
references to useQuery, GetSponsorsDataDocument, sponsorsData, loading, and
error so reviewers can find the changes.

---

Nitpick comments:
In `@backend/apps/owasp/admin/sponsor.py`:
- Around line 13-29: Add the "status" field to the admin configuration so admins
can view and filter by Draft/Active/Archived; update the ModelAdmin's
list_display tuple to include "status" (alongside "name", "sort_name", etc.) and
add "status" to the list_filter tuple so it appears in filters — locate and edit
the admin class that defines list_display and list_filter (e.g., SponsorAdmin)
and insert "status" into both tuples.

In `@backend/apps/owasp/api/internal/mutations/sponsor.py`:
- Line 19: The function docstring """Create a new sponsor.""" in the sponsor
module has a blank line immediately after it; remove that empty line so the
docstring is followed directly by the function body (i.e., edit the
create_sponsor function in sponsor.py to eliminate the blank line after the
docstring to satisfy D202).
- Line 31: The log call logger.info("Sponsor created successfully") lacks
context—update the logging in the sponsor creation flow (the logger.info call in
sponsor.py inside the sponsor creation function) to include an identifier from
the created sponsor (e.g., sponsor.id, sponsor.key or
sponsor.name/created_sponsor.name) so the message becomes descriptive (e.g.,
"Sponsor created successfully: <identifier>"). Ensure you reference the same
variable name used in the function that holds the created sponsor object.

In `@backend/apps/owasp/api/internal/queries/sponsor.py`:
- Around line 14-42: The duplicated sponsor-type priority mapping used in the
sponsors and active_sponsors resolvers should be extracted to a single reusable
symbol: create a module-level constant (e.g., SPONSOR_PRIORITY) or a small
helper function (e.g., get_sponsor_priority) that maps Sponsor.SponsorType
values to priority integers, then update sponsors() and active_sponsors() to use
that constant/helper in their key functions (replace the inline dict and lambda
logic with key=lambda x: SPONSOR_PRIORITY[x.sponsor_type] or
key=get_sponsor_priority), ensuring both resolvers share the same source of
truth.

In
`@backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py`:
- Around line 14-23: The ForeignKey additions for model_name='sponsor' (fields
name='chapter' and name='project' added via migrations.AddField) set null=True
but not blank=True, causing admin forms to treat them as required; update those
migration field definitions to include blank=True on both the chapter and
project ForeignKey declarations so forms treat them as optional (i.e., add
blank=True alongside null=True and on_delete=... in the migrations.AddField
entries for 'chapter' and 'project').

In `@frontend/src/app/sponsors/apply/page.tsx`:
- Around line 1-12: The sponsor application page CreateProgramPage exposes the
CreateSponsor mutation via createSponsor and SponsorForm without any
authentication or anti-abuse controls; either enforce the same
session/permission checks used elsewhere (e.g., call
useSession()/getServerSession and verify project leader role before rendering or
invoking createSponsor) or, if public submissions are intended, add anti-abuse
measures (rate limiting or CAPTCHA integration around SponsorForm submissions
and server-side throttling) so anonymous requests cannot spam the mutation;
update the rendering logic and mutation handler to gate calls accordingly and
reuse existing auth helpers used by other pages to keep behavior consistent.

In `@frontend/src/app/sponsors/page.tsx`:
- Around line 79-85: Replace the navigation button that calls
router.push('/sponsors/apply') with a Next.js Link to '/sponsors/apply' to
improve semantics, accessibility, and enable prefetching; update the JSX by
swapping the <button> using onClick={...router.push...} for a <Link> that
preserves the same className and inner text ("Apply here for Sponsorship"), and
remove the useRouter import/usage from this page component if it is no longer
referenced.
- Around line 11-17: Replace the local Sponsor type with the generated GraphQL
type to avoid schema drift: remove the local "type Sponsor" and import the
appropriate generated type that corresponds to GetSponsorsDataDocument (e.g.,
GetSponsorsData or GetSponsorsDataQuery) from your GraphQL codegen output, then
use that imported type wherever "Sponsor" was referenced (for example in props,
state, or fetch result handling) so the component relies on the generated schema
types instead of a manually defined one.

In `@frontend/src/components/SponsorForm.tsx`:
- Around line 173-186: The FormTextarea usage is inconsistent with
FormTextInput's API (Textarea uses onChange while TextInput uses onValueChange);
update the SponsorForm.tsx to use the same prop name across both components —
either switch the FormTextarea call to onValueChange (calling
handleInputChange('message', value) and setTouched(...) with the new value
parameter) or refactor FormTextInput to also accept onChange so both components
share the same handler name; locate FormTextarea and FormTextInput usages and
adjust the prop name and corresponding handler (handleInputChange, setTouched,
errors, touched) so the API is consistent project-wide.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a2817fd1-b688-494f-b95f-9e7c9d6a3be5

📥 Commits

Reviewing files that changed from the base of the PR and between 6f1df81 and 7e03fa5.

⛔ Files ignored due to path filters (3)
  • frontend/src/types/__generated__/graphql.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/sponsorMutations.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/sponsorQueries.generated.ts is excluded by !**/__generated__/**
📒 Files selected for processing (16)
  • backend/apps/api/rest/v0/project.py
  • backend/apps/owasp/admin/sponsor.py
  • backend/apps/owasp/api/internal/mutations/sponsor.py
  • backend/apps/owasp/api/internal/nodes/sponsor.py
  • backend/apps/owasp/api/internal/queries/sponsor.py
  • backend/apps/owasp/migrations/0073_sponsor_contact_email_sponsor_created_at.py
  • backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py
  • backend/apps/owasp/migrations/0075_sponsor_message.py
  • backend/apps/owasp/models/sponsor.py
  • backend/settings/graphql.py
  • docker-compose/local/compose.yaml
  • frontend/src/app/sponsors/apply/page.tsx
  • frontend/src/app/sponsors/page.tsx
  • frontend/src/components/SponsorForm.tsx
  • frontend/src/server/mutations/sponsorMutations.ts
  • frontend/src/server/queries/sponsorQueries.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 10 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docker-compose/local/compose.yaml">

<violation number="1" location="docker-compose/local/compose.yaml:150">
P2: Renaming all compose named volumes to ticket-suffixed names (`*_4259`) breaks existing cleanup automation. The `backend/Makefile` cleanup targets reference the old volume names (`nest-local_backend-venv`, `nest-local_cache-data`) which won't match the new suffixed volumes, causing cleanup failures and leaving orphaned volumes on developer/CI machines. Additionally, this change appears unrelated to the PR's stated purpose of adding sponsors program support.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
frontend/src/app/sponsors/page.tsx (3)

93-99: Consider using Link instead of button with router.push for navigation.

Using a Link component for navigation to /sponsors/apply provides better accessibility (keyboard navigation, right-click context menu) and allows browsers to prefetch the destination. The button approach works but is less semantically correct for navigation.

♻️ Proposed fix using Link
-          <button
-            type="button"
-            className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
-            onClick={() => router.push('/sponsors/apply')}
-          >
-            Apply here for Sponsorship
-          </button>
+          <Link
+            href="/sponsors/apply"
+            className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
+          >
+            Apply here for Sponsorship
+          </Link>

This also allows removing useRouter import and hook if no longer needed elsewhere.

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

In `@frontend/src/app/sponsors/page.tsx` around lines 93 - 99, The current
navigation uses a button with onClick calling router.push('/sponsors/apply'),
which is less semantic and harms accessibility; replace the button with
Next.js's Link component pointing to '/sponsors/apply' (preserve the same
className and children text "Apply here for Sponsorship") so the element behaves
as a proper navigational link and allows prefetch/context menu, and remove the
useRouter import and router hook if they are no longer used elsewhere in the
component.

101-111: Add spacing between the CTA card and sponsors grid.

There's no margin or gap between the SecondaryCard and the sponsors grid/empty message below it. Consider adding vertical spacing for better visual separation.

♻️ Proposed fix to add spacing
       </SecondaryCard>
 
-      {sponsorsData?.activeSponsors && sponsorsData?.activeSponsors.length > 0 ? (
+      <div className="mt-8">
+      {sponsorsData?.activeSponsors && sponsorsData.activeSponsors.length > 0 ? (
         <div className="grid grid-cols-1 justify-items-center gap-6 sm:grid-cols-2 xl:grid-cols-4">
-          {sponsorsData.activeSponsors.map((sponsor: Sponsor) => (
+          {sponsorsData.activeSponsors.map((sponsor) => (
             <SponsorEntryCard key={sponsor.id} sponsor={sponsor} />
           ))}
         </div>
       ) : (
         <p className="text-center text-lg font-medium">No Sponsors Found</p>
       )}
+      </div>

Note: Also removed the redundant optional chain on the second sponsorsData?.activeSponsors since it's already checked in the condition.

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

In `@frontend/src/app/sponsors/page.tsx` around lines 101 - 111, Add vertical
spacing between the SecondaryCard and the sponsors block by applying a top
margin or gap to the container that follows it (e.g., wrap the conditional block
or the grid div with a class like "mt-6" or similar) so the CTA and the sponsors
grid/empty message are visually separated; also simplify the condition by
removing the redundant optional chaining on sponsorsData?.activeSponsors in the
map (use sponsorsData.activeSponsors.map and keep the initial null/length check
as-is). Ensure the change targets the JSX around SecondaryCard, the conditional
rendering of sponsorsData.activeSponsors, and the SponsorEntryCard list.

11-17: Consider using generated GraphQL types or marking fields as optional.

The local Sponsor type declares all fields as required strings, but GraphQL responses typically include nullable fields (e.g., imageUrl, description, url). Using this type for the type assertion at line 105 could mask runtime nulls.

Either import and use the generated type from GetSponsorsDataDocument, or mark potentially nullable fields as optional:

♻️ Proposed fix using optional fields
 type Sponsor = {
   id: string
   name: string
-  url: string
-  description: string
-  imageUrl: string
+  url?: string | null
+  description?: string | null
+  imageUrl?: string | null
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/sponsors/page.tsx` around lines 11 - 17, The local Sponsor
type currently marks all fields as non-null strings which can mask nullable
GraphQL responses; update the code to either import and use the generated
GraphQL type (from the GetSponsorsDataDocument/its generated types) in place of
the local Sponsor type where the data is asserted (around the usage at the type
assertion near line 105), or change the local Sponsor definition so fields that
can be null in GraphQL (e.g., imageUrl, description, url) are optional/nullable
(string | null) and then update any downstream code to handle possible
null/undefined values accordingly.
backend/apps/owasp/models/sponsor.py (1)

76-76: Consider adding verbose_name for consistency.

Most other fields in this model include an explicit verbose_name. Adding one here would maintain consistency with the model's style.

♻️ Suggested change
-    status = models.CharField(max_length=20, choices=StatusType.choices, default=StatusType.DRAFT)
+    status = models.CharField(
+        verbose_name="Status",
+        max_length=20,
+        choices=StatusType.choices,
+        default=StatusType.DRAFT,
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/owasp/models/sponsor.py` at line 76, The Sponsor model's status
field (status = models.CharField(..., choices=StatusType.choices,
default=StatusType.DRAFT)) lacks an explicit verbose_name which breaks
consistency with other fields; update this field to include a concise
verbose_name (e.g., verbose_name="status" or "Status") by adding the
verbose_name argument to the models.CharField declaration so it matches the
model's styling conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/owasp/api/internal/mutations/__init__.py`:
- Line 3: The file exporting SponsorMutation is missing a final newline (Ruff
W292); open the file containing the import of SponsorMutation (the line "from
.sponsor import SponsorMutation") and add a single trailing newline character at
EOF so the file ends with a newline to satisfy the linter.

In `@frontend/src/app/sponsors/page.tsx`:
- Around line 52-60: The Link currently uses sponsor.url directly which can be
null/undefined/empty; wrap the Link (and its FaArrowUpRightFromSquare icon) in a
conditional that only renders when sponsor.url is a non-empty string (e.g.,
sponsor.url && sponsor.url.trim() !== '') or after a simple URL validation;
otherwise render a fallback (plain text like "Visit website" without a link or
nothing) so you never produce an invalid anchor by rendering Link with a
missing/empty URL.

---

Nitpick comments:
In `@backend/apps/owasp/models/sponsor.py`:
- Line 76: The Sponsor model's status field (status = models.CharField(...,
choices=StatusType.choices, default=StatusType.DRAFT)) lacks an explicit
verbose_name which breaks consistency with other fields; update this field to
include a concise verbose_name (e.g., verbose_name="status" or "Status") by
adding the verbose_name argument to the models.CharField declaration so it
matches the model's styling conventions.

In `@frontend/src/app/sponsors/page.tsx`:
- Around line 93-99: The current navigation uses a button with onClick calling
router.push('/sponsors/apply'), which is less semantic and harms accessibility;
replace the button with Next.js's Link component pointing to '/sponsors/apply'
(preserve the same className and children text "Apply here for Sponsorship") so
the element behaves as a proper navigational link and allows prefetch/context
menu, and remove the useRouter import and router hook if they are no longer used
elsewhere in the component.
- Around line 101-111: Add vertical spacing between the SecondaryCard and the
sponsors block by applying a top margin or gap to the container that follows it
(e.g., wrap the conditional block or the grid div with a class like "mt-6" or
similar) so the CTA and the sponsors grid/empty message are visually separated;
also simplify the condition by removing the redundant optional chaining on
sponsorsData?.activeSponsors in the map (use sponsorsData.activeSponsors.map and
keep the initial null/length check as-is). Ensure the change targets the JSX
around SecondaryCard, the conditional rendering of sponsorsData.activeSponsors,
and the SponsorEntryCard list.
- Around line 11-17: The local Sponsor type currently marks all fields as
non-null strings which can mask nullable GraphQL responses; update the code to
either import and use the generated GraphQL type (from the
GetSponsorsDataDocument/its generated types) in place of the local Sponsor type
where the data is asserted (around the usage at the type assertion near line
105), or change the local Sponsor definition so fields that can be null in
GraphQL (e.g., imageUrl, description, url) are optional/nullable (string | null)
and then update any downstream code to handle possible null/undefined values
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22a849c4-7e6f-4c32-8f73-4d7bad1c44cc

📥 Commits

Reviewing files that changed from the base of the PR and between 7e03fa5 and 1cc02f0.

📒 Files selected for processing (10)
  • backend/apps/owasp/api/internal/mutations/__init__.py
  • backend/apps/owasp/api/internal/mutations/sponsor.py
  • backend/apps/owasp/migrations/0073_sponsor_contact_email_sponsor_created_at.py
  • backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py
  • backend/apps/owasp/migrations/0075_sponsor_message.py
  • backend/apps/owasp/models/sponsor.py
  • backend/settings/graphql.py
  • docker-compose/local/compose.yaml
  • frontend/src/app/sponsors/apply/page.tsx
  • frontend/src/app/sponsors/page.tsx
✅ Files skipped from review due to trivial changes (4)
  • backend/apps/owasp/migrations/0075_sponsor_message.py
  • backend/apps/owasp/migrations/0073_sponsor_contact_email_sponsor_created_at.py
  • backend/apps/owasp/migrations/0074_sponsor_chapter_sponsor_project_sponsor_status.py
  • docker-compose/local/compose.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/settings/graphql.py
  • frontend/src/app/sponsors/apply/page.tsx
  • backend/apps/owasp/api/internal/mutations/sponsor.py

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
frontend/src/app/sponsors/page.tsx (2)

11-17: Consider using the generated GraphQL type instead of a local definition.

The Sponsor type duplicates the shape from the generated GetSponsorsDataQuery['activeSponsors'][number]. Using the generated type ensures type safety stays in sync with the GraphQL schema.

♻️ Proposed refactor
-type Sponsor = {
-  id: string
-  name: string
-  url: string
-  description: string
-  imageUrl: string
-}
+import type { GetSponsorsDataQuery } from 'types/__generated__/sponsorQueries.generated'
+
+type Sponsor = GetSponsorsDataQuery['activeSponsors'][number]

Note: Adjust the existing import on line 8 to include both the document and the type.

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

In `@frontend/src/app/sponsors/page.tsx` around lines 11 - 17, The local Sponsor
type duplicates the GraphQL-generated type and risks drifting from the schema;
replace the custom type definition in page.tsx with the generated type
GetSponsorsDataQuery['activeSponsors'][number] by importing the generated
query/types (add to the existing import on line 8) and update any references to
Sponsor to use that generated type instead (e.g., type annotations and props) so
the component uses the canonical GraphQL type.

94-100: Consider using Link instead of button with router.push for navigation.

Using Link provides better semantics (anchor element), accessibility (keyboard navigation, right-click "open in new tab"), and automatic prefetching.

♻️ Proposed refactor
-          <button
-            type="button"
-            className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
-            onClick={() => router.push('/sponsors/apply')}
-          >
+          <Link
+            href="/sponsors/apply"
+            className="inline-flex items-center justify-center rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-800"
+          >
             Apply here for Sponsorship
-          </button>
+          </Link>

This also allows removing the useRouter import and hook call if not used elsewhere.

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

In `@frontend/src/app/sponsors/page.tsx` around lines 94 - 100, Replace the
navigation button that calls router.push with a Next.js Link anchor: swap the
<button> that uses onClick={() => router.push('/sponsors/apply')} for a <Link
href="/sponsors/apply"> element and apply the same className and inner text to
that Link, and then remove the useRouter import and const router = useRouter()
call if they’re no longer used; ensure you import Link from 'next/link' at the
top of the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/app/sponsors/page.tsx`:
- Around line 41-63: SponsorEntryCard currently never renders
Sponsor.description; update the component to conditionally render
sponsor.description (e.g., check sponsor.description && ...) under the
organization name (inside SponsorEntryCard, after the div that shows
{sponsor.name} and before the link) as a short paragraph with appropriate
styling (small/gray or muted text, optional line-clamp/truncate) so the optional
short description from the Sponsor type is visible for each entry.
- Around line 19-39: The SponsorLogo component may throw when next/image loads
from remote domains not allowed by next.config.ts; update SponsorLogo to handle
image load failures by adding an onError handler on the Image component that
switches rendering to the placeholder fallback (same UI as the FaBuilding block)
and ensure any cleanup state (e.g., a local boolean like hasImageError) is used
to conditionally render the placeholder; alternatively, if you prefer whitelist
changes, add the missing sponsor domains to next.config.ts remotePatterns so
Image can load them without runtime errors.

---

Nitpick comments:
In `@frontend/src/app/sponsors/page.tsx`:
- Around line 11-17: The local Sponsor type duplicates the GraphQL-generated
type and risks drifting from the schema; replace the custom type definition in
page.tsx with the generated type GetSponsorsDataQuery['activeSponsors'][number]
by importing the generated query/types (add to the existing import on line 8)
and update any references to Sponsor to use that generated type instead (e.g.,
type annotations and props) so the component uses the canonical GraphQL type.
- Around line 94-100: Replace the navigation button that calls router.push with
a Next.js Link anchor: swap the <button> that uses onClick={() =>
router.push('/sponsors/apply')} for a <Link href="/sponsors/apply"> element and
apply the same className and inner text to that Link, and then remove the
useRouter import and const router = useRouter() call if they’re no longer used;
ensure you import Link from 'next/link' at the top of the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a4c12538-1ed1-4ee7-ba54-111eb6adc60a

📥 Commits

Reviewing files that changed from the base of the PR and between 1cc02f0 and 5f434b7.

📒 Files selected for processing (2)
  • backend/apps/owasp/api/internal/mutations/__init__.py
  • frontend/src/app/sponsors/page.tsx
✅ Files skipped from review due to trivial changes (1)
  • backend/apps/owasp/api/internal/mutations/init.py

cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 25, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 25, 2026
cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 27, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 4 files (changes from recent commits).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/components/SponsorForm.tsx (1)

44-86: Consolidate duplicated validation definitions to prevent drift.

Lines 44-68 and Lines 81-86 repeat the same field-validator mapping. If a new field is added, one path can be missed and submit behavior can diverge from inline errors.

Refactor sketch
+  const fieldValidators = {
+    name: () => validateName(formData.name),
+    website: () => validateWebsite(formData.website),
+    contactEmail: () => validateContactEmail(formData.contactEmail),
+    message: () => validateMessage(formData.message),
+  } as const
+
   const errors = useFormValidation(
-    [
-      {
-        field: 'name',
-        shouldValidate: touched.name ?? false,
-        validator: () => validateName(formData.name),
-      },
-      {
-        field: 'website',
-        shouldValidate: touched.website ?? false,
-        validator: () => validateWebsite(formData.website),
-      },
-      {
-        field: 'contactEmail',
-        shouldValidate: touched.contactEmail ?? false,
-        validator: () => validateContactEmail(formData.contactEmail),
-      },
-      {
-        field: 'message',
-        shouldValidate: touched.message ?? false,
-        validator: () => validateMessage(formData.message),
-      },
-    ],
+    Object.entries(fieldValidators).map(([field, validator]) => ({
+      field,
+      shouldValidate: touched[field] ?? false,
+      validator,
+    })),
     [formData, touched]
   )
@@
-    if (
-      validateName(formData.name) ||
-      validateWebsite(formData.website) ||
-      validateContactEmail(formData.contactEmail) ||
-      validateMessage(formData.message)
-    ) {
+    const hasErrors = Object.values(fieldValidators).some((validate) => !!validate())
+    if (hasErrors) {
       return
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/SponsorForm.tsx` around lines 44 - 86, The validation
rules are duplicated between the useFormValidation call and the handleSubmit
checks which can drift; consolidate by defining a single validators structure
(e.g., an array or object named validators or validationRules) that maps fields
to their shouldValidate flag and validator functions (using validateName,
validateWebsite, validateContactEmail, validateMessage), then pass that
structure into useFormValidation and reuse it in handleSubmit to set nextTouched
and to run the submit-time validation check (instead of calling validateXxx
individually); update setTouched to derive keys from this validators structure
so adding a field requires one change only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/forms/shared/formValidationUtils.ts`:
- Around line 41-42: The URL parsing uses the raw input variable value directly
(const parsedUrl = new URL(value)), which rejects valid inputs with
leading/trailing spaces; trim the input first (e.g., const trimmed =
value.trim()) and use that trimmed string when constructing new URL and when
checking parsedUrl.protocol so behavior matches the email trimming on line 55;
update any subsequent uses in the same validation function to use the trimmed
variable.

---

Nitpick comments:
In `@frontend/src/components/SponsorForm.tsx`:
- Around line 44-86: The validation rules are duplicated between the
useFormValidation call and the handleSubmit checks which can drift; consolidate
by defining a single validators structure (e.g., an array or object named
validators or validationRules) that maps fields to their shouldValidate flag and
validator functions (using validateName, validateWebsite, validateContactEmail,
validateMessage), then pass that structure into useFormValidation and reuse it
in handleSubmit to set nextTouched and to run the submit-time validation check
(instead of calling validateXxx individually); update setTouched to derive keys
from this validators structure so adding a field requires one change only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a9a36b88-f127-4031-a3a2-a906a336303c

📥 Commits

Reviewing files that changed from the base of the PR and between 164f3ee and 2365f35.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • frontend/package.json
  • frontend/src/components/SponsorForm.tsx
  • frontend/src/components/forms/shared/formValidationUtils.ts
✅ Files skipped from review due to trivial changes (1)
  • frontend/package.json

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/components/forms/shared/formValidationUtils.ts">

<violation number="1" location="frontend/src/components/forms/shared/formValidationUtils.ts:42">
P2: Website validation became case-sensitive for scheme prefix, causing valid URLs like `HTTPS://...` to be rejected.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 28, 2026
cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 28, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

@Utkarsh-0304 Utkarsh-0304 marked this pull request as ready for review March 28, 2026 03:36
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

@Utkarsh-0304
Copy link
Copy Markdown
Contributor Author

Hi @arkid15r, please review this and let me know if any changes are required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Sponsors Program Support

1 participant