Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const nextConfig: NextConfig = {

// Code-splitting optimization for heavy libraries
experimental: {
optimizePackageImports: ['@monaco-editor/react', 'video.js', 'ethers'],
optimizePackageImports: ['@monaco-editor/react', 'video.js', 'ethers', 'recharts', 'framer-motion', 'date-fns'],
},

modularizeImports: {
Expand Down
23 changes: 19 additions & 4 deletions src/__tests__/sms/queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,22 @@
*/

import { SMSQueue } from '@/lib/sms/queue';
import { TwilioProvider } from '@/lib/sms/provider';
import { SMSMessage } from '@/lib/sms/types';

// Mock the TwilioProvider to return success immediately (no credentials in test env)
const mockSend = vi.fn().mockResolvedValue({ success: true, provider: 'twilio', messageId: 'mock-id' });
vi.mock('@/lib/sms/provider', () => {
function MockTwilioProvider() {
this.type = 'twilio';
this.send = mockSend;
}
return {
TwilioProvider: MockTwilioProvider as unknown as typeof import('@/lib/sms/provider').TwilioProvider,
};
});

import { TwilioProvider } from '@/lib/sms/provider';

describe('SMSQueue', () => {
let queue: SMSQueue;
let provider: TwilioProvider;
Expand Down Expand Up @@ -251,9 +264,11 @@ describe('SMSQueue', () => {

const logs = queue.getDeliveryLogs();

if (logs.length > 0) {
expect(logs[0].metadata).toBeDefined();
}
expect(logs.length).toBeGreaterThan(0);
// Check the most recent log (last in the array) has metadata
const lastLog = logs[logs.length - 1];
expect(lastLog.metadata).toBeDefined();
expect(lastLog.metadata?.userId).toBe('user123');
});
});
});
16 changes: 16 additions & 0 deletions src/app/api/certificates/__tests__/certificate-security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ import {
import { slidingWindowRateLimit } from '@/lib/ratelimit';
import { appendAuditLog, queryAuditLogs } from '@/lib/audit';

// Mock the DB pool so generateCertificate's completion check passes
vi.mock('@/lib/db/pool', () => ({
query: vi.fn().mockResolvedValue({
rows: [
{
user_id: 'user-123',
course_id: 'course-123',
progress: 100,
completed_lessons: [],
last_accessed_at: new Date().toISOString(),
completed_at: new Date().toISOString(),
},
],
}),
}));

describe('Certificate Security', () => {
const mockUserId = 'user-123';
const mockCourseId = 'course-123';
Expand Down
27 changes: 13 additions & 14 deletions src/components/profile/ConferenceManagement.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState, useCallback, useMemo } from 'react';
import { useState, useCallback, useEffect } from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Calendar, MapPin, Link as LinkIcon, Plus, Trash2, Edit2, AlertCircle } from 'lucide-react';
Expand Down Expand Up @@ -42,6 +42,7 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM

const [conferences, setConferences] = useState<Conference[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [initialLoading, setInitialLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [editingId, setEditingId] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
Expand All @@ -53,7 +54,7 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM
role: 'attendee',
date: new Date().toISOString().split('T')[0],
location: '',
url: '',
url: undefined,
},
mode: 'onSubmit',
});
Expand All @@ -63,7 +64,7 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM
// Load conferences on mount
const loadConferences = useCallback(async () => {
try {
setIsLoading(true);
setInitialLoading(true);
setError(null);
const data = await getConferences(effectiveUserId);
setConferences(data);
Expand All @@ -72,18 +73,14 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM
setError(errorMsg);
toast.error(errorMsg);
} finally {
setIsLoading(false);
setInitialLoading(false);
}
}, [effectiveUserId]);

// Initialize loading on mount (in production, this would be a useEffect)
const isInitialized = useMemo(() => {
if (conferences.length === 0 && !isLoading && !editingId) {
// In a real scenario, useEffect would handle this
// For now, rely on parent component or manual invocation
}
return true;
}, [conferences.length, isLoading, editingId]);
// Load conferences on mount
useEffect(() => {
loadConferences();
}, []); // eslint-disable-line react-hooks/exhaustive-deps

const onSubmit = async (data: ConferenceFormData) => {
try {
Expand Down Expand Up @@ -120,7 +117,7 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM
setValue('role', conference.role);
setValue('date', conference.date);
setValue('location', conference.location || '');
setValue('url', conference.url || '');
setValue('url', conference.url ?? undefined);
setShowForm(true);
};

Expand Down Expand Up @@ -281,6 +278,8 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM
<SubmitButton
isLoading={isLoading}
loadingText="Saving..."
type="button"
onClick={() => methods.handleSubmit(onSubmit)()}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:bg-blue-400 disabled:cursor-not-allowed dark:bg-blue-600 dark:hover:bg-blue-700"
>
{editingId ? 'Update Conference' : 'Add Conference'}
Expand All @@ -299,7 +298,7 @@ export default function ConferenceManagement({ userId: propUserId }: ConferenceM
aria-label={showForm ? undefined : 'No conferences added yet'}
>
<p className="text-sm">
{isLoading ? 'Loading conferences...' : 'No conferences yet. Add one to get started!'}
{initialLoading ? 'Loading conferences...' : 'No conferences yet. Add one to get started!'}
</p>
</div>
) : (
Expand Down
Loading
Loading