Skip to content

Add signature status badge and logo upload functionality#333

Merged
Starefossen merged 15 commits intomainfrom
sponsor-phase-ii
Feb 13, 2026
Merged

Add signature status badge and logo upload functionality#333
Starefossen merged 15 commits intomainfrom
sponsor-phase-ii

Conversation

@Starefossen
Copy link
Copy Markdown
Member

@Starefossen Starefossen commented Feb 13, 2026

User description

Introduce a SignatureBadge component to display the signature status of sponsors and implement logo upload functionality in the SponsorOnboardingForm. Enhance the OrganizerCombobox and SponsorCombobox stories for better clarity, improve styling and accessibility in the SponsorContactRoleSelect, and add a send contract mutation to the sponsor router with validation schema.


PR Type

Enhancement, Tests


Description

  • Add signature status badge display to SponsorCard with days pending calculation

  • Implement multi-step SendContractModal for contract generation and sending workflow

  • Create SendContractButton component integrated into SponsorCRMForm header

  • Add comprehensive test coverage for contract schemas and CRM utilities

  • Implement SponsorOnboardingLogoUpload component for SVG logo variants

  • Enhance dark mode support across onboarding form and related components

  • Add SVG sanitization utility to prevent XSS attacks in logo uploads

  • Create contract email templates migration for Sanity CMS


Diagram Walkthrough

flowchart LR
  A["SendContractButton"] -->|opens| B["SendContractModal"]
  B -->|step 1| C["Contract Readiness Check"]
  C -->|step 2| D["PDF Preview"]
  D -->|step 3| E["Confirm & Send"]
  E -->|calls| F["sendContract Mutation"]
  F -->|updates| G["SponsorForConference"]
  G -->|displays| H["SignatureBadge"]
  I["SponsorOnboardingForm"] -->|includes| J["SponsorOnboardingLogoUpload"]
  J -->|sanitizes| K["SVG Content"]
Loading

File Walkthrough

Relevant files
Tests
8 files
contract-schemas.test.ts
Comprehensive test coverage for contract template schemas
+383/-0 
contract-status-schemas.test.ts
Test coverage for contract and signature status schemas   
+207/-0 
crm-utils.test.ts
Test coverage for CRM utility functions including signature badges
+280/-0 
onboarding.test.ts
Additional test cases for onboarding URL building               
+20/-0   
svg.test.ts
Comprehensive test coverage for SVG sanitization utility 
+100/-0 
SendContractButton.stories.tsx
Create Storybook stories for SendContractButton component
+113/-0 
SponsorCard.stories.tsx
Add signature status badge stories to SponsorCard               
+50/-0   
SponsorOnboardingLogoUpload.stories.tsx
Create Storybook stories for logo upload component             
+54/-0   
Configuration changes
2 files
index.ts
Seed contract-related email templates for Sanity CMS         
+114/-0 
preview.tsx
Add SendContractButton and SponsorOnboardingLogoUpload to Storybook
+2/-1     
Enhancement
14 files
utils.ts
Add signature status badge and days pending utility functions
+28/-0   
sponsor.ts
Implement sendContract mutation with PDF generation and logging
+105/-1 
contractTemplate.ts
Add SendContractSchema for contract sending validation     
+8/-0     
EmptyState.stories.tsx
Add dark mode classes to EmptyState story variants             
+3/-3     
SendContractButton.tsx
Implement SendContractButton with modal trigger and state management
+53/-0   
SendContractModal.tsx
Implement multi-step SendContractModal with readiness, preview,
confirm
+346/-0 
SponsorCRMForm.tsx
Integrate SendContractButton into CRM form header               
+8/-0     
SponsorCard.tsx
Add SignatureBadge component display in contract view       
+49/-0   
OrganizerCombobox.stories.tsx
Enhance Storybook documentation and rename mock interface
+9/-57   
SponsorCombobox.stories.tsx
Enhance Storybook documentation and rename mock interface
+9/-68   
SponsorContactRoleSelect.tsx
Improve styling and accessibility of placeholder option   
+4/-2     
SponsorOnboardingForm.tsx
Integrate logo upload component and enhance dark mode support
+76/-43 
SponsorOnboardingLogoUpload.tsx
Implement logo upload component with SVG sanitization and preview
+167/-0 
svg.ts
Add SVG sanitization utility to prevent XSS attacks           

- Introduced SignatureBadge component to display the signature status of sponsors.
- Added utility functions for handling signature status and calculating days pending.
- Updated SponsorCard to conditionally render the SignatureBadge based on the current view and sponsor's signature status.

refactor: enhance OrganizerCombobox and SponsorCombobox stories

- Updated stories to include documentation descriptions for better clarity.
- Changed interface names from Organizer to MockOrganizer and Sponsor to MockSponsor for consistency.

chore: remove documentation sections from OrganizerCombobox and SponsorCombobox stories

- Cleaned up unused documentation sections to streamline story files.

fix: improve styling and accessibility in SponsorContactRoleSelect

- Added gray text color for placeholder option in the select dropdown.
- Enhanced styling for better visibility when no value is selected.

feat: implement logo upload functionality in SponsorOnboardingForm

- Added SponsorOnboardingLogoUpload component for uploading primary and bright logo variants.
- Integrated logo upload functionality into the SponsorOnboardingForm.
- Updated form to handle logo state and display appropriate upload fields.

feat: add send contract mutation to sponsor router

- Implemented sendContract mutation to handle contract generation and sending.
- Added logging for contract status changes and integrated PDF generation.

feat: create SendContractSchema for contract sending validation

- Defined SendContractSchema to validate inputs for sending contracts.
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cloudnativedays Ready Ready Preview, Comment Feb 13, 2026 2:28pm

Request Review

@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns

XSS risk (SVG sanitization):
The onboarding logo upload sanitizes SVG via sanitizeSvg and then renders it via InlineSvg. This is a good step, but SVG sanitization is notoriously hard to get right with ad-hoc regex-based stripping (e.g., xlink:href, href in other elements, data: URLs, style/url() injections, nested/encoded payloads, unknown namespaces). Please verify sanitizeSvg uses a robust SVG-safe sanitizer (or a strict allowlist parser) and that InlineSvg does not reintroduce risk via dangerouslySetInnerHTML without additional safeguards.

⚡ Recommended focus areas for review

Data Integrity

The new sendContract mutation updates signatureStatus to pending unconditionally, even when signerEmail is omitted (the UI describes that as “skip digital signing”). Consider whether signatureStatus should remain not-started unless a signer is provided, and ensure state transitions remain consistent with the rest of the CRM workflow (e.g., contract resent vs first send, already-sent contracts, etc.).

sendContract: adminProcedure
  .input(SendContractSchema)
  .mutation(async ({ input, ctx }) => {
    const { sponsorForConference: sfc, error: sfcError } =
      await getSponsorForConference(input.sponsorForConferenceId)
    if (sfcError || !sfc) {
      throw new TRPCError({
        code: 'NOT_FOUND',
        message: 'Sponsor relationship not found',
        cause: sfcError,
      })
    }

    const { template, error: templateError } = await getContractTemplate(
      input.templateId,
    )
    if (templateError || !template) {
      throw new TRPCError({
        code: 'NOT_FOUND',
        message: 'Contract template not found',
        cause: templateError,
      })
    }

    const primaryContact =
      sfc.contactPersons?.find((c) => c.isPrimary) ||
      sfc.contactPersons?.[0]

    // Generate the PDF
    const pdfBuffer = await generateContractPdf(template, {
      sponsor: {
        name: sfc.sponsor.name,
        orgNumber: sfc.sponsor.orgNumber,
        address: sfc.sponsor.address,
        website: sfc.sponsor.website,
      },
      contactPerson: primaryContact
        ? { name: primaryContact.name, email: primaryContact.email }
        : undefined,
      tier: sfc.tier
        ? { title: sfc.tier.title, tagline: sfc.tier.tagline }
        : undefined,
      addons: sfc.addons?.map((a) => ({ title: a.title })),
      contractValue: sfc.contractValue,
      contractCurrency: sfc.contractCurrency,
      conference: {
        title: sfc.conference.title,
        startDate: sfc.conference.startDate,
        endDate: sfc.conference.endDate,
        city: sfc.conference.city,
        organizer: sfc.conference.organizer,
        organizerOrgNumber: sfc.conference.organizerOrgNumber,
        organizerAddress: sfc.conference.organizerAddress,
        venueName: sfc.conference.venueName,
        venueAddress: sfc.conference.venueAddress,
        sponsorEmail: sfc.conference.sponsorEmail,
      },
    })

    // Update CRM record: contract status, signer email, sent timestamp
    const now = getCurrentDateTime()
    const updateFields: Record<string, unknown> = {
      contractStatus: 'contract-sent',
      contractSentAt: now,
      signatureStatus: 'pending',
      contractTemplate: { _type: 'reference', _ref: input.templateId },
    }
    if (input.signerEmail) {
      updateFields.signerEmail = input.signerEmail
    }
Performance

The mutation returns the full generated PDF as a base64 string in the response. This can create large tRPC payloads, memory pressure, and slow UI rendering. Consider returning a short-lived download URL / storing the PDF in object storage, or at least validating/limiting PDF size and ensuring the client doesn’t hold it longer than needed.

// Generate the PDF
const pdfBuffer = await generateContractPdf(template, {
  sponsor: {
    name: sfc.sponsor.name,
    orgNumber: sfc.sponsor.orgNumber,
    address: sfc.sponsor.address,
    website: sfc.sponsor.website,
  },
  contactPerson: primaryContact
    ? { name: primaryContact.name, email: primaryContact.email }
    : undefined,
  tier: sfc.tier
    ? { title: sfc.tier.title, tagline: sfc.tier.tagline }
    : undefined,
  addons: sfc.addons?.map((a) => ({ title: a.title })),
  contractValue: sfc.contractValue,
  contractCurrency: sfc.contractCurrency,
  conference: {
    title: sfc.conference.title,
    startDate: sfc.conference.startDate,
    endDate: sfc.conference.endDate,
    city: sfc.conference.city,
    organizer: sfc.conference.organizer,
    organizerOrgNumber: sfc.conference.organizerOrgNumber,
    organizerAddress: sfc.conference.organizerAddress,
    venueName: sfc.conference.venueName,
    venueAddress: sfc.conference.venueAddress,
    sponsorEmail: sfc.conference.sponsorEmail,
  },
})

// Update CRM record: contract status, signer email, sent timestamp
const now = getCurrentDateTime()
const updateFields: Record<string, unknown> = {
  contractStatus: 'contract-sent',
  contractSentAt: now,
  signatureStatus: 'pending',
  contractTemplate: { _type: 'reference', _ref: input.templateId },
}
if (input.signerEmail) {
  updateFields.signerEmail = input.signerEmail
}

await clientWrite
  .patch(input.sponsorForConferenceId)
  .set(updateFields)
  .commit()

// TODO: Send contract email via Resend with PDF attachment
// When Posten signering (#303) is integrated, this is where the
// signing flow should be initiated instead of manual email.

// Log activity
const userId = ctx.speaker._id
if (userId) {
  const oldStatus = sfc.contractStatus
  try {
    await logContractStatusChange(
      input.sponsorForConferenceId,
      oldStatus,
      'contract-sent',
      userId,
    )
  } catch (logError) {
    console.error('Failed to log contract send activity:', logError)
  }
}

return {
  success: true,
  pdf: pdfBuffer.toString('base64'),
  filename: `contract-${sfc.sponsor.name.toLowerCase().replace(/\s+/g, '-')}.pdf`,
}
Edge Cases

getDaysPending uses Math.floor on the date difference and can return negative values if contractSentAt is in the future (clock skew/timezones) and can be off-by-one around day boundaries. Consider clamping at 0 and/or using a date library or normalized UTC midnight comparison if “days pending” needs to be stable.

export function getDaysPending(contractSentAt?: string): number | null {
  if (!contractSentAt) return null
  const sent = new Date(contractSentAt)
  const now = new Date()
  return Math.floor((now.getTime() - sent.getTime()) / (1000 * 60 * 60 * 24))
}

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Feb 13, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Implement the full digital signing flow

The PR's new contract sending feature is incomplete because it lacks integration
with a digital signing service. The suggestion is to implement this missing
integration to provide a complete, automated workflow.

Examples:

src/components/admin/sponsor-crm/SendContractModal.tsx [264-273]
            <div className="rounded-md border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20">
              <div className="flex items-center gap-2">
                <ExclamationTriangleIcon className="h-5 w-5 shrink-0 text-amber-500" />
                <p className="text-sm text-amber-700 dark:text-amber-300">
                  Digital signing via Posten signering is not yet integrated.
                  The contract will be generated and the status updated, but
                  signing must be handled manually.
                </p>
              </div>
            </div>
src/server/routers/sponsor.ts [1148-1151]
        // TODO: Send contract email via Resend with PDF attachment
        // When Posten signering (#303) is integrated, this is where the
        // signing flow should be initiated instead of manual email.

Solution Walkthrough:

Before:

// file: src/server/routers/sponsor.ts

sendContract: adminProcedure
  .input(SendContractSchema)
  .mutation(async ({ input, ctx }) => {
    // ... logic to get data and generate PDF ...

    // Update CRM record
    await clientWrite
      .patch(input.sponsorForConferenceId)
      .set({ contractStatus: 'contract-sent', signatureStatus: 'pending' })
      .commit()

    // TODO: Send contract email via Resend with PDF attachment
    // When Posten signering (#303) is integrated, this is where the
    // signing flow should be initiated instead of manual email.

    // Log activity
    // ...
    return { success: true }
  })

After:

// file: src/server/routers/sponsor.ts
import { initiateDigitalSigning } from '@/lib/signing-service'

sendContract: adminProcedure
  .input(SendContractSchema)
  .mutation(async ({ input, ctx }) => {
    // ... logic to get data and generate PDF ...

    // Initiate digital signing flow
    const signingResult = await initiateDigitalSigning({
      pdfBuffer,
      signerEmail: input.signerEmail,
      sponsorName: sfc.sponsor.name,
    });

    // Update CRM record with signing provider details
    await clientWrite
      .patch(input.sponsorForConferenceId)
      .set({
        contractStatus: 'contract-sent',
        signatureStatus: 'pending',
        signingProviderId: signingResult.id,
      })
      .commit()

    // Log activity
    return { success: true }
  })
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that the core "Send Contract" feature is incomplete, as the crucial digital signing integration is missing and marked as a TODO, which significantly limits the feature's utility.

High
Possible issue
Log signature status transition
Suggestion Impact:The commit adds logging of signature status transitions by calling logSignatureStatusChange (transitioning from previous status/default 'not-started' to 'pending') when sending a contract, gated on signerEmail being provided.

code diff:

@@ -1152,16 +1170,30 @@
         // Log activity
         const userId = ctx.speaker._id
         if (userId) {
-          const oldStatus = sfc.contractStatus
+          const oldContractStatus = sfc.contractStatus
           try {
             await logContractStatusChange(
               input.sponsorForConferenceId,
-              oldStatus,
+              oldContractStatus,
               'contract-sent',
               userId,
             )
           } catch (logError) {
             console.error('Failed to log contract send activity:', logError)
+          }
+
+          if (input.signerEmail) {
+            const oldSignatureStatus = sfc.signatureStatus ?? 'not-started'
+            try {
+              await logSignatureStatusChange(
+                input.sponsorForConferenceId,
+                oldSignatureStatus,
+                'pending',
+                userId,
+              )
+            } catch (logError) {
+              console.error('Failed to log signature status change:', logError)
+            }
           }
         }

In the sendContract mutation, add a call to logSignatureStatusChange to record
the transition to 'pending', ensuring the activity log is complete.

src/server/routers/sponsor.ts [1152-1166]

-// Log activity
+// Log contract status change
 const userId = ctx.speaker._id
 if (userId) {
-  const oldStatus = sfc.contractStatus
+  const oldContract = sfc.contractStatus
   try {
     await logContractStatusChange(
       input.sponsorForConferenceId,
-      oldStatus,
+      oldContract,
       'contract-sent',
       userId,
     )
   } catch (logError) {
     console.error('Failed to log contract send activity:', logError)
   }
+  // Log signature status change
+  const oldSig = sfc.signatureStatus
+  try {
+    await logSignatureStatusChange(
+      input.sponsorForConferenceId,
+      oldSig ?? 'not-started',
+      'pending',
+      userId,
+    )
+  } catch (sigError) {
+    console.error('Failed to log signature status change:', sigError)
+  }
 }

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a missing piece of logic. Logging the signatureStatus change is crucial for maintaining a complete audit trail in the sponsor activity timeline, which is a key feature of the CRM.

Medium
Validate required data before PDF generation
Suggestion Impact:The commit added a validation guard that checks for conference title, startDate, and endDate on the sfc object and throws a PRECONDITION_FAILED TRPCError before proceeding, preventing PDF generation with incomplete conference data.

code diff:

+        if (
+          !sfc.conference?.title ||
+          !sfc.conference.startDate ||
+          !sfc.conference.endDate
+        ) {
+          throw new TRPCError({
+            code: 'PRECONDITION_FAILED',
+            message: 'Conference data is incomplete for contract generation',
+          })
+        }

In the sendContract mutation, add a validation check to ensure essential
conference data exists on the sfc object before calling generateContractPdf to
prevent errors from incomplete data.

src/server/routers/sponsor.ts [1101-1129]

+if (
+  !sfc.conference?.title ||
+  !sfc.conference.startDate ||
+  !sfc.conference.endDate
+) {
+  throw new TRPCError({
+    code: 'PRECONDITION_FAILED',
+    message: 'Conference data is incomplete for contract generation.',
+  })
+}
+
 const pdfBuffer = await generateContractPdf(template, {
   sponsor: {
     name: sfc.sponsor.name,
     orgNumber: sfc.sponsor.orgNumber,
     address: sfc.sponsor.address,
     website: sfc.sponsor.website,
   },
   contactPerson: primaryContact
     ? { name: primaryContact.name, email: primaryContact.email }
     : undefined,
   tier: sfc.tier
     ? { title: sfc.tier.title, tagline: sfc.tier.tagline }
     : undefined,
   addons: sfc.addons?.map((a) => ({ title: a.title })),
   contractValue: sfc.contractValue,
   contractCurrency: sfc.contractCurrency,
   conference: {
     title: sfc.conference.title,
     startDate: sfc.conference.startDate,
     endDate: sfc.conference.endDate,
     city: sfc.conference.city,
     organizer: sfc.conference.organizer,
     organizerOrgNumber: sfc.conference.organizerOrgNumber,
     organizerAddress: sfc.conference.organizerAddress,
     venueName: sfc.conference.venueName,
     venueAddress: sfc.conference.venueAddress,
     sponsorEmail: sfc.conference.sponsorEmail,
   },
 })

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: This is a valuable defensive programming suggestion that improves the robustness of the sendContract endpoint by validating necessary data before it's used, preventing potential runtime errors from incomplete CMS data.

Medium
General
Improve type safety for status colors
Suggestion Impact:SIGNATURE_COLORS was retyped to Record, string> and the status lookup was updated with a keyof typeof SIGNATURE_COLORS cast (using ?? for fallback).

code diff:

-const SIGNATURE_COLORS: Record<string, string> = {
+const SIGNATURE_COLORS: Record<
+  Exclude<SignatureStatus, 'not-started'>,
+  string
+> = {
   pending:
     'bg-yellow-100 text-yellow-700 ring-yellow-700/20 dark:bg-yellow-900/30 dark:text-yellow-400 dark:ring-yellow-400/20',
   signed:
@@ -298,12 +301,14 @@
 }) {
   const { label } = getSignatureStatusBadgeProps(status)
   const days = getDaysPending(contractSentAt)
-  const colorClass = SIGNATURE_COLORS[status] || SIGNATURE_COLORS.pending
+  const colorClass =
+    SIGNATURE_COLORS[status as keyof typeof SIGNATURE_COLORS] ??
+    SIGNATURE_COLORS.pending

Improve type safety by changing the type of SIGNATURE_COLORS to
Record<Exclude<SignatureStatus, 'not-started'>, string> to ensure all signature
statuses have a corresponding color defined.

src/components/admin/sponsor-crm/SponsorCard.tsx [281-301]

-const SIGNATURE_COLORS: Record<string, string> = {
+const SIGNATURE_COLORS: Record<Exclude<SignatureStatus, 'not-started'>, string> = {
   pending:
     'bg-yellow-100 text-yellow-700 ring-yellow-700/20 dark:bg-yellow-900/30 dark:text-yellow-400 dark:ring-yellow-400/20',
   signed:
     'bg-green-100 text-green-700 ring-green-700/20 dark:bg-green-900/30 dark:text-green-400 dark:ring-green-400/20',
   rejected:
     'bg-red-100 text-red-700 ring-red-700/20 dark:bg-red-900/30 dark:text-red-400 dark:ring-red-400/20',
   expired:
     'bg-orange-100 text-orange-700 ring-orange-700/20 dark:bg-orange-900/30 dark:text-orange-400 dark:ring-orange-400/20',
 }
 
 function SignatureBadge({
   status,
   contractSentAt,
 }: {
   status: SignatureStatus
   contractSentAt?: string
 }) {
   const { label } = getSignatureStatusBadgeProps(status)
   const days = getDaysPending(contractSentAt)
-  const colorClass = SIGNATURE_COLORS[status] || SIGNATURE_COLORS.pending
+  const colorClass =
+    SIGNATURE_COLORS[status as keyof typeof SIGNATURE_COLORS] ||
+    SIGNATURE_COLORS.pending
 ...

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly proposes a way to improve type safety for the SIGNATURE_COLORS map, making the code more maintainable and robust against future changes to the SignatureStatus type.

Low
Avoid using alert() for user feedback
Suggestion Impact:The alert() call was removed and replaced with component state-based error handling (useState + setError) and an inline error message rendered in the UI.

code diff:

-import { useRef } from 'react'
+import { useRef, useState } from 'react'
 import { InlineSvg } from '@/components/InlineSvg'
 import {
   ArrowDownTrayIcon,
@@ -40,14 +40,16 @@
   onChange: (svg: string | null) => void
 }) {
   const fileInputRef = useRef<HTMLInputElement>(null)
+  const [error, setError] = useState<string | null>(null)
 
   const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
     const file = event.target.files?.[0]
     if (!file) return
     if (file.type !== 'image/svg+xml') {
-      alert('Please select an SVG file.')
+      setError('Please select an SVG file.')
       return
     }
+    setError(null)
     const reader = new FileReader()
     reader.onload = (e) => {
       const svgContent = e.target?.result as string
@@ -120,6 +122,9 @@
           onChange={handleFileUpload}
           className="hidden"
         />
+        {error && (
+          <p className="mt-2 text-sm text-red-600 dark:text-red-400">{error}</p>
+        )}

In handleFileUpload, replace the disruptive alert() with a non-blocking UI
notification for a better user experience when an invalid file type is uploaded.

src/components/sponsor/SponsorOnboardingLogoUpload.tsx [44-57]

 const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
   const file = event.target.files?.[0]
   if (!file) return
   if (file.type !== 'image/svg+xml') {
-    alert('Please select an SVG file.')
+    // TODO: Replace with a non-blocking UI error message
+    console.error('Invalid file type. Please select an SVG file.')
     return
   }
   const reader = new FileReader()
   reader.onload = (e) => {
     const svgContent = e.target?.result as string
     onChange(sanitizeSvg(svgContent))
   }
   reader.readAsText(file)
 }

[Suggestion processed]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that using alert() is poor UX. Replacing it with a non-blocking notification is a valid improvement for the component's quality.

Low
  • Update

…itorPage components; update preview with new components
@Starefossen Starefossen linked an issue Feb 13, 2026 that may be closed by this pull request
5 tasks
@Starefossen Starefossen merged commit 533d456 into main Feb 13, 2026
9 checks passed
@Starefossen Starefossen deleted the sponsor-phase-ii branch February 13, 2026 14:33
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.

Sponsor Self-Service Onboarding Portal

1 participant