diff --git a/FrontEnd/my-app/app/[locale]/quests/[id]/page.tsx b/FrontEnd/my-app/app/[locale]/quests/[id]/page.tsx index a8d6c9b48..6173cc197 100644 --- a/FrontEnd/my-app/app/[locale]/quests/[id]/page.tsx +++ b/FrontEnd/my-app/app/[locale]/quests/[id]/page.tsx @@ -32,7 +32,7 @@ export default function QuestDetailPage() { setIsLoading(true); setError(null); const data = await getQuestById(questId); - setQuest(data as unknown as Quest); + setQuest(data); trackEvent(ANALYTICS_EVENTS.QUEST_VIEW, { questId }); } catch (err) { setError(err instanceof Error ? err : new Error('Failed to fetch quest')); diff --git a/FrontEnd/my-app/components/homepage/FeaturedQuests.test.tsx b/FrontEnd/my-app/components/homepage/FeaturedQuests.test.tsx index 90be10f48..957f06d36 100644 --- a/FrontEnd/my-app/components/homepage/FeaturedQuests.test.tsx +++ b/FrontEnd/my-app/components/homepage/FeaturedQuests.test.tsx @@ -48,7 +48,7 @@ describe('FeaturedQuests - Error Boundary and Timeout Integration', () => { const mockError = new Error('API request failed'); mockGetQuests .mockRejectedValueOnce(mockError) - .mockResolvedValueOnce({ quests: [] }); + .mockResolvedValueOnce({ data: [] }); render(); @@ -96,7 +96,7 @@ describe('FeaturedQuests - Error Boundary and Timeout Integration', () => { ); mockGetQuests .mockRejectedValueOnce(timeoutError) - .mockResolvedValueOnce({ quests: [] }); + .mockResolvedValueOnce({ data: [] }); render(); @@ -130,7 +130,7 @@ describe('FeaturedQuests - Error Boundary and Timeout Integration', () => { ); mockGetQuests .mockRejectedValueOnce(timeoutError) - .mockResolvedValueOnce({ quests: [] }); + .mockResolvedValueOnce({ data: [] }); render(); diff --git a/FrontEnd/my-app/components/homepage/FeaturedQuests.tsx b/FrontEnd/my-app/components/homepage/FeaturedQuests.tsx index 96bcf2bda..81099e835 100644 --- a/FrontEnd/my-app/components/homepage/FeaturedQuests.tsx +++ b/FrontEnd/my-app/components/homepage/FeaturedQuests.tsx @@ -56,14 +56,11 @@ function FeaturedQuestsContent() { customTimeout, { onRevalidate: (fresh) => { - const freshItems = - (fresh as any).data ?? (fresh as any).quests ?? []; - setQuests(freshItems); + setQuests(fresh.data); }, } ); - const items = (res as any).data ?? (res as any).quests ?? []; - setQuests(items); + setQuests(res.data); setRetryCount(0); // Reset retry count on success } catch (err) { // Don't show error if request was cancelled (e.g., component unmounting) diff --git a/FrontEnd/my-app/components/quest/QuestHeader.tsx b/FrontEnd/my-app/components/quest/QuestHeader.tsx index a0952c3be..19df481fd 100644 --- a/FrontEnd/my-app/components/quest/QuestHeader.tsx +++ b/FrontEnd/my-app/components/quest/QuestHeader.tsx @@ -130,7 +130,7 @@ export function QuestHeader({ > {category} - {difficulty && difficultyInfo && ( + {difficultyInfo && ( model. + */ +export function mapPaginatedQuests(dto: any): PaginatedResponse { + if (!dto) { + return { + data: [], + total: 0, + page: 1, + limit: 12, + totalPages: 0, + }; + } + + // Support both backend format (`quests`) and legacy mock server format (`data`) + const questsRaw = dto.quests || dto.data || []; + const data = Array.isArray(questsRaw) ? questsRaw.map(mapQuest) : []; + + const meta = dto.meta || {}; + const page = dto.page ?? meta.page ?? 1; + const limit = dto.limit ?? meta.limit ?? 12; + const total = dto.total ?? meta.total ?? data.length; + const totalPages = dto.totalPages ?? meta.totalPages ?? Math.ceil(total / limit); + + return { + data, + total, + page, + limit, + totalPages, + }; +} + +/** + * Maps a raw UserResponse to a UI UserProfile domain model. + */ +export function mapUserProfile( + dto: UserResponse, + isOwnProfile = false, + isFollowing = false +): UserProfile { + return { + id: dto.id || '', + username: dto.username || '', + stellarAddress: dto.stellarAddress || '', + avatar: dto.avatarUrl || '', + bio: dto.bio || '', + level: dto.level || 1, + xp: dto.xp || 0, + totalEarnings: parseFloat(dto.totalEarned || '0'), + questsCompleted: dto.questsCompleted || 0, + currentStreak: 0, // default, not in UserResponse + joinDate: dto.createdAt || new Date().toISOString(), + lastActive: dto.lastActiveAt || dto.updatedAt || new Date().toISOString(), + isFollowing: isOwnProfile ? false : isFollowing, + followersCount: 0, // default + followingCount: 0, // default + isOwnProfile, + }; +} + +/** + * Maps a raw UserStatsResponse to a UI UserStats domain model. + */ +export function mapUserStats(dto: UserStatsResponse): UserStatsResponse { + return { + xp: dto.xp || 0, + level: dto.level || 1, + totalEarned: String(dto.totalEarned || '0'), + questsCompleted: dto.questsCompleted || 0, + failedQuests: dto.failedQuests || 0, + successRate: dto.successRate || 0, + badges: dto.badges || [], + lastActiveAt: dto.lastActiveAt, + }; +} + +/** + * Maps a raw SubmissionResponse to a UI Submission domain model. + */ +export function mapSubmission(dto: SubmissionResponse): Submission { + return { + id: dto.id || '', + questId: dto.questId || '', + userId: dto.userId || '', + status: dto.status as ApiSubmissionStatus, + proof: dto.proof || {}, + rejectionReason: dto.rejectionReason, + approvedAt: dto.approvedAt, + approvedBy: dto.approvedBy, + rejectedAt: dto.rejectedAt, + rejectedBy: dto.rejectedBy, + createdAt: dto.createdAt || new Date().toISOString(), + updatedAt: dto.updatedAt || new Date().toISOString(), + quest: dto.quest ? { + id: dto.quest.id, + title: dto.quest.title, + rewardAmount: dto.quest.rewardAmount, + rewardAsset: dto.quest.rewardAsset, + } : undefined, + }; +} + +/** + * Helper to map a Badge ID or registry entry to a full Badge. + */ +export function mapBadgeIdToBadge(id: string): Badge { + const registry: Record> = { + 'badge-1': { + name: 'Fast Finisher', + description: 'Completed 10 quests before deadline.', + icon: 'bolt', + rarity: 'rare', + }, + 'badge-2': { + name: 'Code Guardian', + description: 'Delivered multiple high-quality review submissions.', + icon: 'shield', + rarity: 'epic', + }, + }; + + const registered = registry[id] || { + name: id + .split('-') + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(' '), + description: 'Earned achievement badge.', + icon: 'award', + rarity: 'common', + }; + + return { + id, + ...registered, + earnedAt: new Date().toISOString(), + }; +} diff --git a/FrontEnd/my-app/lib/api/quests.integration.test.ts b/FrontEnd/my-app/lib/api/quests.integration.test.ts index c3268bc81..951f572c1 100644 --- a/FrontEnd/my-app/lib/api/quests.integration.test.ts +++ b/FrontEnd/my-app/lib/api/quests.integration.test.ts @@ -37,23 +37,22 @@ afterEach(() => { describe('Quests API Integration Tests', () => { it('should fetch quests successfully with mock data', async () => { - const response = (await getQuests()) as any; + const response = await getQuests(); expect(response).toBeDefined(); expect(response.data).toHaveLength(1); expect(response.data[0].id).toBe('quest-1'); expect(response.data[0].title).toBe('Test Quest 1'); - expect(response.meta.total).toBe(1); + expect(response.total).toBe(1); }); it('should fetch a single quest by ID successfully', async () => { - const response = (await getQuestById('quest-123')) as any; + const response = await getQuestById('quest-123'); expect(response).toBeDefined(); - expect(response.data).toBeDefined(); - expect(response.data.id).toBe('quest-123'); - expect(response.data.title).toBe('Test Quest quest-123'); - expect(response.data.rewardAmount).toBe(50); + expect(response.id).toBe('quest-123'); + expect(response.title).toBe('Test Quest quest-123'); + expect(response.rewardAmount).toBe(50); }); it('should reuse fresh cached quest listings without another request', async () => { @@ -65,8 +64,8 @@ describe('Quests API Integration Tests', () => { }) ); - const first = (await getQuests({ limit: 10 })) as any; - const second = (await getQuests({ limit: 10 })) as any; + const first = await getQuests({ limit: 10 }); + const second = await getQuests({ limit: 10 }); expect(requestCount).toBe(1); expect(first.data[0].id).toBe('quest-1'); @@ -85,11 +84,11 @@ describe('Quests API Integration Tests', () => { }) ); - const fresh = (await getQuests()) as any; + const fresh = await getQuests(); now = 3 * 60 * 1000 + 1; - const stale = (await getQuests(undefined, undefined, undefined, { + const stale = await getQuests(undefined, undefined, undefined, { onRevalidate, - })) as any; + }); expect(stale.data[0].id).toBe(fresh.data[0].id); expect(stale.data[0].id).toBe('quest-1'); @@ -115,9 +114,9 @@ describe('Quests API Integration Tests', () => { }) ); - const first = (await getQuests()) as any; + const first = await getQuests(); now = 13 * 60 * 1000 + 1; - const second = (await getQuests()) as any; + const second = await getQuests(); expect(requestCount).toBe(2); expect(first.data[0].id).toBe('quest-1'); diff --git a/FrontEnd/my-app/lib/api/quests.serialization.test.ts b/FrontEnd/my-app/lib/api/quests.serialization.test.ts index 5fca503dd..0019a9442 100644 --- a/FrontEnd/my-app/lib/api/quests.serialization.test.ts +++ b/FrontEnd/my-app/lib/api/quests.serialization.test.ts @@ -510,8 +510,8 @@ describe('getQuests – integration via MSW fixture', () => { const result = await getQuests(); - expect(Array.isArray(result.quests)).toBe(true); - expect(result.quests).toHaveLength(questPaginatedFixture.quests.length); + expect(Array.isArray(result.data)).toBe(true); + expect(result.data).toHaveLength(questPaginatedFixture.quests.length); expect(result.total).toBe(questPaginatedFixture.total); expect(result.page).toBe(questPaginatedFixture.page); expect(result.limit).toBe(questPaginatedFixture.limit); @@ -527,7 +527,7 @@ describe('getQuests – integration via MSW fixture', () => { const result = await getQuests(); - result.quests.forEach((quest) => { + result.data.forEach((quest) => { assertRequiredQuestFields(quest); }); }); @@ -541,7 +541,7 @@ describe('getQuests – integration via MSW fixture', () => { const result = await getQuests(); - expect(result.quests).toHaveLength(0); + expect(result.data).toHaveLength(0); expect(result.total).toBe(0); }); diff --git a/FrontEnd/my-app/lib/api/quests.ts b/FrontEnd/my-app/lib/api/quests.ts index 2f6c64981..94b0f3d3a 100644 --- a/FrontEnd/my-app/lib/api/quests.ts +++ b/FrontEnd/my-app/lib/api/quests.ts @@ -20,18 +20,18 @@ import { } from './client'; import { cacheManager } from '@/lib/utils/cache'; import type { - QuestResponse, - PaginatedQuestsResponse, CreateQuestRequest, UpdateQuestRequest, QuestQueryParams, } from '@/lib/types/api.types'; +import type { Quest, PaginatedResponse } from '@/lib/types/quest'; +import { mapQuest, mapPaginatedQuests } from './mappers'; const QUEST_LIST_TTL_MS = 3 * 60 * 1000; const QUEST_LIST_STALE_TTL_MS = 10 * 60 * 1000; type QuestListCacheOptions = { - onRevalidate?: (data: PaginatedQuestsResponse) => void; + onRevalidate?: (data: PaginatedResponse) => void; }; // Re-export legacy types for backward compatibility with existing hooks @@ -56,20 +56,22 @@ export async function getQuests( cancelToken?: CancelToken, timeout?: number, cacheOptions?: QuestListCacheOptions -): Promise { +): Promise> { const params = buildQuestParams(filters); const cacheKey = `${generateQuestsCacheKey(params)}${timeout ? `:t-${timeout}` : ''}`; return cacheManager.getStaleWhileRevalidate( cacheKey, - () => - withRetry(() => - get('/quests', { + async () => { + const raw = await withRetry(() => + get('/quests', { params, signal: cancelToken?.signal, timeout, }) - ), + ); + return mapPaginatedQuests(raw); + }, { ttl: QUEST_LIST_TTL_MS, staleTtl: QUEST_LIST_STALE_TTL_MS, @@ -89,15 +91,17 @@ export async function getQuests( export async function getQuestById( id: string, cancelToken?: CancelToken -): Promise { +): Promise { return cacheManager.get( `quest-${id}`, - () => - withRetry(() => - get(`/quests/${id}`, { + async () => { + const raw = await withRetry(() => + get(`/quests/${id}`, { signal: cancelToken?.signal, }) - ), + ); + return mapQuest(raw); + }, 60_000 ); } @@ -108,11 +112,11 @@ export async function getQuestById( export async function createQuest( payload: CreateQuestRequest -): Promise { - const result = await post('/quests', payload); +): Promise { + const result = await post('/quests', payload); // Invalidate list cache (no simple key, so just clear all quest entries) cacheManager.clear(); - return result; + return mapQuest(result); } // --------------------------------------------------------------------------- @@ -122,10 +126,10 @@ export async function createQuest( export async function updateQuest( id: string, payload: UpdateQuestRequest -): Promise { - const result = await patch(`/quests/${id}`, payload); +): Promise { + const result = await patch(`/quests/${id}`, payload); cacheManager.invalidate(`quest-${id}`); - return result; + return mapQuest(result); } // --------------------------------------------------------------------------- diff --git a/FrontEnd/my-app/lib/api/user.ts b/FrontEnd/my-app/lib/api/user.ts index e41cedfc8..f504de738 100644 --- a/FrontEnd/my-app/lib/api/user.ts +++ b/FrontEnd/my-app/lib/api/user.ts @@ -30,6 +30,7 @@ import type { } from '@/lib/types/api.types'; import type { QuestResponse, SubmissionResponse } from '@/lib/types/api.types'; +import { mapQuest, mapSubmission, mapUserStats, mapBadgeIdToBadge } from './mappers'; // Re-export legacy dashboard types for backward compat export type { @@ -211,11 +212,12 @@ export async function fetchUserStats( address: string, cancelToken?: CancelToken ): Promise { - return withRetry(() => + const raw = await withRetry(() => get(`/users/${address}/stats`, { signal: cancelToken?.signal, }) ); + return mapUserStats(raw); } // --------------------------------------------------------------------------- @@ -323,15 +325,30 @@ export async function fetchBadges(): Promise { */ export async function fetchDashboardData( address?: string -): Promise< - DashboardData | { userProfile: UserResponse; userStats: UserStatsResponse } -> { +): Promise { if (address) { - const [userProfile, userStats] = await Promise.all([ - fetchUserByAddress(address), - fetchUserStats(address), - ]); - return { userProfile, userStats }; + const [userStats, activeQuests, recentSubmissions, earningsHistory, badges] = + await Promise.all([ + fetchUserStats(address), + fetchActiveQuests(), + fetchRecentSubmissions(), + fetchEarningsHistory(), + fetchBadges(), + ]); + + const stats = mapUserStats(userStats); + + const mappedBadges = stats.badges && stats.badges.length > 0 + ? stats.badges.map(mapBadgeIdToBadge) + : badges; + + return { + stats, + activeQuests: activeQuests.map(mapQuest), + recentSubmissions: recentSubmissions.map(mapSubmission), + earningsHistory, + badges: mappedBadges, + }; } const [activeQuests, recentSubmissions, earningsHistory, badges] = @@ -343,9 +360,9 @@ export async function fetchDashboardData( ]); return { - stats: mockUserStats, - activeQuests, - recentSubmissions, + stats: mapUserStats(mockUserStats), + activeQuests: activeQuests.map(mapQuest), + recentSubmissions: recentSubmissions.map(mapSubmission), earningsHistory, badges, }; diff --git a/FrontEnd/my-app/lib/hooks/useAdmin.ts b/FrontEnd/my-app/lib/hooks/useAdmin.ts index 8b1bd94d7..68fda3ee7 100644 --- a/FrontEnd/my-app/lib/hooks/useAdmin.ts +++ b/FrontEnd/my-app/lib/hooks/useAdmin.ts @@ -8,11 +8,11 @@ import { useContext, } from 'react'; import type { - Quest, + AdminQuest as Quest, AdminStats, AdminUser, QuestFormData, - QuestStatus, + AdminQuestStatus as QuestStatus, BulkOperation, Notification, } from '../types/admin'; diff --git a/FrontEnd/my-app/lib/hooks/useQuests.ts b/FrontEnd/my-app/lib/hooks/useQuests.ts index ce9af3c76..e213ef9eb 100644 --- a/FrontEnd/my-app/lib/hooks/useQuests.ts +++ b/FrontEnd/my-app/lib/hooks/useQuests.ts @@ -48,7 +48,7 @@ export function useQuests( ...memoizedFilters, ...memoizedPagination, }); - setQuests(response.quests as any); + setQuests(response.data); setPagination({ page: response.page ?? 1, limit: response.limit ?? 12, diff --git a/FrontEnd/my-app/lib/hooks/useUserStats.ts b/FrontEnd/my-app/lib/hooks/useUserStats.ts index b0d436810..96772d962 100644 --- a/FrontEnd/my-app/lib/hooks/useUserStats.ts +++ b/FrontEnd/my-app/lib/hooks/useUserStats.ts @@ -50,9 +50,9 @@ export function useUserStats(): UseUserStatsReturn { setError(null); try { - const data = (await fetchDashboardData( + const data = await fetchDashboardData( user.stellarAddress - )) as DashboardData; + ); setStats(data.stats); setActiveQuests(data.activeQuests); setRecentSubmissions(data.recentSubmissions); @@ -96,7 +96,7 @@ export function useStats() { return; } fetchUserStats(user.stellarAddress) - .then((data) => setStats(data as any)) + .then((data) => setStats(data)) .catch((err) => setError(err.message)) .finally(() => setIsLoading(false)); }, [user?.stellarAddress]); diff --git a/FrontEnd/my-app/lib/types/admin.ts b/FrontEnd/my-app/lib/types/admin.ts index ef8b22464..bacef0156 100644 --- a/FrontEnd/my-app/lib/types/admin.ts +++ b/FrontEnd/my-app/lib/types/admin.ts @@ -1,10 +1,10 @@ -export type QuestStatus = +export type AdminQuestStatus = | 'draft' | 'active' | 'paused' | 'completed' | 'cancelled'; -export type QuestDifficulty = +export type AdminQuestDifficulty = | 'beginner' | 'intermediate' | 'advanced' @@ -17,14 +17,14 @@ export type QuestCategory = | 'Testing' | 'Community'; -export interface Quest { +export interface AdminQuest { id: string; title: string; description: string; shortDescription: string; category: QuestCategory; - difficulty?: QuestDifficulty; - status: QuestStatus; + difficulty?: AdminQuestDifficulty; + status: AdminQuestStatus; reward: number; xpReward: number; deadline: string; @@ -42,7 +42,7 @@ export interface QuestFormData { description: string; shortDescription: string; category: QuestCategory; - difficulty: QuestDifficulty; + difficulty: AdminQuestDifficulty; reward: number; xpReward: number; deadline: string; @@ -82,3 +82,8 @@ export interface Notification { message: string; duration?: number; } + +// Backward compatibility exports +export type QuestStatus = AdminQuestStatus; +export type QuestDifficulty = AdminQuestDifficulty; +export type Quest = AdminQuest; diff --git a/FrontEnd/my-app/lib/types/dashboard.ts b/FrontEnd/my-app/lib/types/dashboard.ts index facbaeb3b..06bce7148 100644 --- a/FrontEnd/my-app/lib/types/dashboard.ts +++ b/FrontEnd/my-app/lib/types/dashboard.ts @@ -1,12 +1,9 @@ -import type { - QuestResponse, - SubmissionResponse, - UserStatsResponse, -} from './api.types'; +import type { UserStatsResponse } from './api.types'; +import type { Quest } from './quest'; +import type { Submission } from './submission'; +export type { Quest, Submission }; export type UserStats = UserStatsResponse; -export type Quest = QuestResponse; -export type Submission = SubmissionResponse; export interface EarningsData { date: string; diff --git a/FrontEnd/my-app/package-lock.json b/FrontEnd/my-app/package-lock.json index 658671e21..fc996dce6 100644 --- a/FrontEnd/my-app/package-lock.json +++ b/FrontEnd/my-app/package-lock.json @@ -21,6 +21,8 @@ "dompurify": "^3.4.1", "framer-motion": "^12.26.2", "lucide-react": "^0.562.0", + "motion": "^12.42.0", + "motion-dom": "^12.42.0", "next": "15.5.18", "next-intl": "^3.26.3", "react": "19.2.5", @@ -8036,21 +8038,6 @@ "ws": "^7.5.1" } }, - "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", @@ -8173,6 +8160,20 @@ "@types/trusted-types": "^2.0.2" } }, + "node_modules/@walletconnect/modal-ui/node_modules/motion": { + "version": "10.16.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-10.16.2.tgz", + "integrity": "sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==", + "license": "MIT", + "dependencies": { + "@motionone/animation": "^10.15.1", + "@motionone/dom": "^10.16.2", + "@motionone/svelte": "^10.16.2", + "@motionone/types": "^10.15.1", + "@motionone/utils": "^10.15.1", + "@motionone/vue": "^10.16.2" + } + }, "node_modules/@walletconnect/relay-api": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", @@ -13262,21 +13263,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, - "node_modules/jayson/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/jayson/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", @@ -14253,17 +14239,29 @@ "license": "MIT" }, "node_modules/motion": { - "version": "10.16.2", - "resolved": "https://registry.npmjs.org/motion/-/motion-10.16.2.tgz", - "integrity": "sha512-p+PurYqfUdcJZvtnmAqu5fJgV2kR0uLFQuBKtLeFVTrYEVllI99tiOTSefVNYuip9ELTEkepIIDftNdze76NAQ==", + "version": "12.42.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.42.0.tgz", + "integrity": "sha512-Qhwvu9sVl5/URSq5CNzwMCpSKK8Uhnrwb6VO977kZyj/wOCS7mWebJUnBoHx5cZU1Zv8a9BD5CSICWKAlrLJgA==", "license": "MIT", "dependencies": { - "@motionone/animation": "^10.15.1", - "@motionone/dom": "^10.16.2", - "@motionone/svelte": "^10.16.2", - "@motionone/types": "^10.15.1", - "@motionone/utils": "^10.15.1", - "@motionone/vue": "^10.16.2" + "framer-motion": "^12.42.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, "node_modules/motion-dom": { @@ -18645,22 +18643,6 @@ "node": ">= 10" } }, - "node_modules/webpack-bundle-analyzer/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", diff --git a/FrontEnd/my-app/package.json b/FrontEnd/my-app/package.json index 37acabe30..08ea5c478 100644 --- a/FrontEnd/my-app/package.json +++ b/FrontEnd/my-app/package.json @@ -36,6 +36,8 @@ "dompurify": "^3.4.1", "framer-motion": "^12.26.2", "lucide-react": "^0.562.0", + "motion": "^12.42.0", + "motion-dom": "^12.42.0", "next": "15.5.18", "next-intl": "^3.26.3", "react": "19.2.5", diff --git a/FrontEnd/my-app/vitest.config.ts b/FrontEnd/my-app/vitest.config.ts index f2cf3c7b9..60488ee3e 100644 --- a/FrontEnd/my-app/vitest.config.ts +++ b/FrontEnd/my-app/vitest.config.ts @@ -12,6 +12,11 @@ export default defineConfig({ environment: 'jsdom', globals: true, setupFiles: ['./tests/setup.ts'], + server: { + deps: { + inline: ['motion', 'motion-dom'], + }, + }, include: [ 'app/**/*.test.{ts,tsx}', 'components/**/*.test.{ts,tsx}',