Skip to content

Conversation

@igorihimbazwe
Copy link
Member

This pull request introduces a major update to the organization's Actions management features, including a new organization-only "Actions" page, expanded API helpers for action management, and improved navigation and user experience for organization users. The changes add support for viewing, creating, updating, and deleting organization actions, and lay the groundwork for a multi-step action creation wizard. Several supporting improvements and new dependencies are also included.

Key changes include:

1. Organization Actions Management UI

  • Replaces the previous static ActionPageLayout with a dynamic, organization-only Actions page. The new UI fetches actions from the backend, supports archiving (deleting) actions, and launches a multi-step "Action Wizard" for creating new actions. It also improves table columns, status handling, and user feedback. Non-organization users are shown a dedicated message. (components/ActionPage/ActionPageLayout.tsx)

2. API Helpers for Action Wizard

  • Adds a comprehensive set of API helper functions for all steps of the action creation wizard, sub-action management, and CRUD operations for actions and sub-actions. Includes support for both JSON and FormData payloads. (helpers/api.ts) [1] [2]

3. Data Model Enhancements

  • Introduces new TypeScript interfaces for OrganizationAction and SubActionSummary, capturing all relevant fields for organization actions and their sub-actions. (types/action.types.ts)

4. Navigation and User Experience

  • Adds an "Actions" item to the main navigation and ensures the navigation highlights "Actions" when on the relevant route. (components/Navigation.tsx) [1] [2]
  • Updates the account info and header components to gracefully handle both user and organization profiles when fetching balances and profile images. (components/AccountInfo.tsx, components/Header.tsx) [1] [2]

5. Dependency Updates

  • Adds new dependencies for PDF generation and manipulation (jspdf, @types/jspdf, pdfjs-dist), which may be used in future features related to actions or tickets. (package.json) [1] [2]

…input for custom fields and webhooks

- Added dark mode styles to the ActionsByAccountPage and related components.
- Introduced KeyValueInput component for managing custom fields and webhooks in the ActionWizardModal.
- Updated Navigation to include Actions in the sidebar.
- Added new SWC modules for improved compatibility across different platforms.
Copilot AI review requested due to automatic review settings December 23, 2025 18:00
@netlify
Copy link

netlify bot commented Dec 23, 2025

Deploy Preview for qiew-code-dev2 ready!

Name Link
🔨 Latest commit 7864a53
🔍 Latest deploy log https://app.netlify.com/projects/qiew-code-dev2/deploys/694ae0955da6860008db29c6
😎 Deploy Preview https://deploy-preview-43--qiew-code-dev2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces comprehensive organization-level actions management with a multi-step wizard, QR code validation, PDF ticket generation, and enhanced user/organization profile handling.

Key Changes:

  • New organization Actions page with create/edit/delete capabilities via multi-step wizard
  • QR object validation system with camera and PDF scanning support
  • API helpers for full CRUD operations on actions and sub-actions
  • Enhanced navigation with "Actions" menu item and profile image fallback logic
  • PDF ticket generation with jsPDF integration

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
types/action.types.ts Defines OrganizationAction and SubActionSummary interfaces for action management
helpers/api.ts Adds API helpers for action wizard steps, sub-actions, and CRUD operations with FormData support
components/Navigation.tsx Adds "Actions" navigation item with route highlighting
components/Header.tsx Implements user/organization profile image fallback using Promise.allSettled
components/AccountInfo.tsx Removes error throwing to enable organization balance fallback
components/ActionPage/QRObjectValidator.tsx New component for scanning and validating QR objects via camera or PDF upload
components/ActionPage/ActionWizardModal.tsx Multi-step wizard for creating/editing actions with 9 configuration steps
components/ActionPage/ActionPageLayout.tsx Replaces static layout with dynamic organization-only actions management
app/welcome/[userId]/page.tsx Adds action display, sub-action modal, and purchase flow for organization profiles
app/action/[userId]/page.tsx New page for managing purchased actions (users) or organization actions with QR validation
package.json Adds jspdf, @types/jspdf, and pdfjs-dist dependencies for PDF generation and parsing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +26 to +35
// Helper for FormData requests (no Content-Type header, let browser set it with boundary)
const apiPostFormData = (url: string, formData: FormData) =>
axios.post(`${baseUrl}${url}`, formData, {
headers: getAuthHeaders()
});

const apiPutFormData = (url: string, formData: FormData) =>
axios.put(`${baseUrl}${url}`, formData, {
headers: getAuthHeaders()
});
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The ApiDelete helper function is defined but the FormData helper functions don't remove the Content-Type header, which is necessary for multipart/form-data requests. The browser needs to set the Content-Type header with the correct boundary parameter. However, getAuthHeaders() likely returns an object with a Content-Type header that should be removed or excluded for FormData requests.

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 62
// First try as user
let response;
try {
response = await getEntityBalance(userId, 'user');
} catch (userError) {
// If user fails, try as organization
response = await getEntityBalance(userId, 'organization');
throw userError;
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The error handling in the catch block removes the user error throwing that was preventing the organization fallback logic from working. However, this creates a logical issue: if the user fetch succeeds, the organizationRes is never checked. The response variable will be set from userRes, but then the code tries to use organizationRes in the catch, which never executes if userRes succeeds. This makes the organization fallback unreachable when user fetch succeeds.

Copilot uses AI. Check for mistakes.
Comment on lines +490 to +492
script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
script.onload = () => {
(window as any).pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The QRObjectValidator component has hardcoded CDN URLs for PDF.js (line 490, 492) that specify version 3.11.174, but pdfjs-dist 5.4.449 is installed via package.json. This version mismatch could cause compatibility issues. The component should either use the installed package version consistently or update the CDN URLs to match the installed version.

Suggested change
script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
script.onload = () => {
(window as any).pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
script.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.449/pdf.min.js";
script.onload = () => {
(window as any).pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.449/pdf.worker.min.js";

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +249
const fetchData = useCallback(
async (targetUserId: string) => {
const token = getToken();
if (!token) {
setError('You need to be logged in to view your actions.');
setLoading(false);
return;
}

const headers = { Authorization: `Bearer ${token}` };

try {
setLoading(true);
setError(null);

const [userRes, organizationRes] = await Promise.allSettled([
axios.get(`${baseUrl}/users/${targetUserId}`, { headers }),
axios.get(`${baseUrl}/organizations/${targetUserId}`, { headers }),
]);

if (userRes.status === 'fulfilled') {
setAccountMode('individual');
const qrResponse = await axios.get(`${baseUrl}/users/${targetUserId}/qr-objects`, { headers });
let qrData = Array.isArray(qrResponse.data?.data) ? qrResponse.data.data : [];

// If an organization is viewing another user's QR objects,
// filter to only show QR objects from their own actions
if (isLoggedInAsOrganization && isViewingAnotherUser && tokenUserId) {
try {
// Fetch the organization's actions to get their action IDs
const orgActionsResponse = await axios.get(
`${baseUrl}/organizations/${tokenUserId}/actions`,
{ headers }
);
const orgActions = orgActionsResponse.data?.data ?? orgActionsResponse.data ?? [];
const orgActionIds = new Set(
Array.isArray(orgActions) ? orgActions.map((action: OrganizationAction) => action.id) : []
);

// Filter QR objects to only include those from the organization's actions
qrData = qrData.filter((qrObj: QrObject) => {
const actionId = qrObj.actionId || qrObj.metadata?.actionId;
const organizationId = qrObj.organizationId || qrObj.metadata?.organizationId;

// Match by actionId or organizationId
return (actionId && orgActionIds.has(actionId)) ||
(organizationId && organizationId === tokenUserId);
});
} catch (filterErr) {
console.error('Failed to filter QR objects by organization:', filterErr);
// If filtering fails, show no QR objects for security
qrData = [];
}
}

setPurchasedActions(qrData);
setOrganizationActions([]);
return;
}

if (organizationRes.status === 'fulfilled') {
setAccountMode('organization');
const searchParams = new URLSearchParams();
if (statusFilter !== 'all') {
searchParams.append('status', statusFilter);
}
const query = searchParams.toString();
const orgActionsResponse = await axios.get(
`${baseUrl}/organizations/${targetUserId}/actions${query ? `?${query}` : ''}`,
{
headers,
},
);
const actionPayload = orgActionsResponse.data?.data ?? orgActionsResponse.data ?? [];
setOrganizationActions(Array.isArray(actionPayload) ? actionPayload : []);
setPurchasedActions([]);
return;
}

throw new Error('Account not found. Please try logging out and back in.');
} catch (err: any) {
const errorMessage =
err?.response?.data?.message ||
err?.message ||
'Something went wrong while loading your actions. Please try again.';
setError(errorMessage);
setPurchasedActions([]);
setOrganizationActions([]);
} finally {
setLoading(false);
}
},
[getToken, statusFilter, isLoggedInAsOrganization, isViewingAnotherUser, tokenUserId],
);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The dependency array for the fetchData useCallback includes statusFilter, but statusFilter is only used for organization accounts. For individual accounts (line 178), changing statusFilter will trigger an unnecessary re-fetch even though the filter doesn't apply. Consider either splitting this into two separate fetch functions or checking accountMode before including statusFilter in the API call.

Copilot uses AI. Check for mistakes.
Comment on lines +302 to +316
if (!coverImage && qrObject.metadata?.actionId) {
try {
const token = getToken();
if (token) {
const headers = { Authorization: `Bearer ${token}` };
const actionResponse = await axios.get(
`${baseUrl}/actions/${qrObject.metadata.actionId}`,
{ headers }
);
coverImage = actionResponse.data?.data?.coverImage || actionResponse.data?.coverImage;
}
} catch (err) {
console.warn('Could not fetch action cover image:', err);
}
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The PDF ticket generation function attempts to fetch the action cover image if not available (lines 302-316), but this is an async operation within the download handler. If the fetch fails or takes time, it doesn't wait for completion before proceeding. The cover image fetching should properly await the result and handle the promise, or remove the async/await pattern since the surrounding function isn't awaiting this operation.

Copilot uses AI. Check for mistakes.
import { useState, useRef, useEffect } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Scan, CheckCircle2, XCircle, Loader2, Camera, X, Upload, FileText, ExternalLink } from "lucide-react";
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Unused import FileText.

Suggested change
import { Scan, CheckCircle2, XCircle, Loader2, Camera, X, Upload, FileText, ExternalLink } from "lucide-react";
import { Scan, CheckCircle2, XCircle, Loader2, Camera, X, Upload, ExternalLink } from "lucide-react";

Copilot uses AI. Check for mistakes.
}

export default function QRObjectValidator({ isOpen, onClose, organizationId }: QRObjectValidatorProps) {
const { getToken, getUserId } = useAuthToken();
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Unused variable getUserId.

Suggested change
const { getToken, getUserId } = useAuthToken();
const { getToken } = useAuthToken();

Copilot uses AI. Check for mistakes.

export default function QRObjectValidator({ isOpen, onClose, organizationId }: QRObjectValidatorProps) {
const { getToken, getUserId } = useAuthToken();
const [isScanning, setIsScanning] = useState(false);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Unused variable isScanning.

Suggested change
const [isScanning, setIsScanning] = useState(false);

Copilot uses AI. Check for mistakes.
const [isScanning, setIsScanning] = useState(false);
const [cameraActive, setCameraActive] = useState(false);
const [error, setError] = useState<string>("");
const [scanResult, setScanResult] = useState<string>("");
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Unused variable scanResult.

Suggested change
const [scanResult, setScanResult] = useState<string>("");
const [, setScanResult] = useState<string>("");

Copilot uses AI. Check for mistakes.
const [subActions, setSubActions] = useState<SubActionSummary[]>([]);
const [subActionsLoading, setSubActionsLoading] = useState(false);
const [existingAction, setExistingAction] = useState<any | null>(null);
const [isEditingExisting, setIsEditingExisting] = useState(false);
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Unused variable isEditingExisting.

Copilot uses AI. Check for mistakes.
@igorihimbazwe igorihimbazwe changed the title Ft-actions Feat actions Dec 23, 2025
@igorihimbazwe igorihimbazwe changed the title Feat actions feat: add actions Dec 23, 2025
@igorihimbazwe igorihimbazwe merged commit 2e7af0e into dev2 Dec 23, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant