Skip to content
Open
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 .github/workflows/accessibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: 'npm'
cache-dependency-path: FrontEnd/my-app/package-lock.json

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
# Setup Node
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: npm
cache-dependency-path: FrontEnd/my-app/package-lock.json

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/frontend-vitest-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: npm
cache-dependency-path: FrontEnd/my-app/package-lock.json

Expand Down
97 changes: 97 additions & 0 deletions FrontEnd/my-app/lib/__tests__/user-dashboard-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { describe, it, expect, expectTypeOf } from 'vitest';

import type {
UserStats,
Quest,
Submission,
EarningsData,
Badge,
DashboardData,
} from '@/lib/api/user';

import { fetchDashboardData } from '@/lib/api/user';

describe('lib/api/user – DashboardData type exports', () => {
it('exports DashboardData as a type-only interface', () => {
// DashboardData must be structurally valid as a type.
const stub: DashboardData = {
stats: {
xp: 0,
level: 1,
questsCompleted: 0,
failedQuests: 0,
successRate: 0,
totalEarned: '0',
badges: [],
},
activeQuests: [],
recentSubmissions: [],
earningsHistory: [],
badges: [],
};

expect(stub).toBeDefined();
expect(stub.stats).toBeDefined();
expect(Array.isArray(stub.activeQuests)).toBe(true);
expect(Array.isArray(stub.recentSubmissions)).toBe(true);
expect(Array.isArray(stub.earningsHistory)).toBe(true);
expect(Array.isArray(stub.badges)).toBe(true);
});

it('exports EarningsData interface', () => {
const data: EarningsData = { date: '2026-01-01', amount: 100 };
expect(data.date).toBe('2026-01-01');
expect(data.amount).toBe(100);
});

it('exports Badge interface with valid rarity', () => {
const badge: Badge = {
id: 'b1',
name: 'Test',
description: 'A test badge',
icon: 'star',
earnedAt: new Date().toISOString(),
rarity: 'rare',
};

expect(badge.rarity).toBe('rare');
});

it('UserStats is assignable from UserStatsResponse shape', () => {
const stats: UserStats = {
xp: 100,
level: 5,
questsCompleted: 10,
failedQuests: 1,
successRate: 90,
totalEarned: '500',
badges: ['b1'],
};

expect(stats.xp).toBe(100);
});

it('exports fetchDashboardData as a function', () => {
expect(typeof fetchDashboardData).toBe('function');
});

it('DashboardData satisfies expected structural shape', () => {
expectTypeOf<DashboardData>().toHaveProperty('stats');
expectTypeOf<DashboardData>().toHaveProperty('activeQuests');
expectTypeOf<DashboardData>().toHaveProperty('recentSubmissions');
expectTypeOf<DashboardData>().toHaveProperty('earningsHistory');
expectTypeOf<DashboardData>().toHaveProperty('badges');
});

it('Quest type alias is usable', () => {
expectTypeOf<Quest>().toHaveProperty('id');
expectTypeOf<Quest>().toHaveProperty('title');
expectTypeOf<Quest>().toHaveProperty('status');
});

it('Submission type alias is usable', () => {
expectTypeOf<Submission>().toHaveProperty('id');
expectTypeOf<Submission>().toHaveProperty('questId');
expectTypeOf<Submission>().toHaveProperty('status');
});
});
36 changes: 36 additions & 0 deletions FrontEnd/my-app/lib/api/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* lib/api/user – public barrel.
*
* Re-exports every public symbol so the rest of the application can
* import from a single path:
*
* import { fetchDashboardData, type DashboardData } from '@/lib/api/user';
*/

// ── Types ──────────────────────────────────────────────────────────────────
export type {
UserStats,
Quest,
Submission,
EarningsData,
Badge,
DashboardData,
} from './types';

// ── API functions & legacy type re-exports ─────────────────────────────────
export {
fetchUserByAddress,
fetchUserStats,
fetchUserQuests,
updateProfile,
searchUsers,
fetchLeaderboard,
deleteAccount,
fetchActiveQuests,
fetchRecentSubmissions,
fetchEarningsHistory,
fetchBadges,
fetchDashboardData,
fetchUserProfile,
updateUserProfile,
} from './user-api';
67 changes: 67 additions & 0 deletions FrontEnd/my-app/lib/api/user/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* User dashboard data types.
*
* These interfaces define the shape of the payload returned by the
* dashboard aggregate endpoint. They are intentionally kept in the
* `lib/api/user` module so consumers can import them from a single
* path: `@/lib/api/user`.
*
* The canonical source definitions live in `@/lib/types/dashboard`
* and `@/lib/types/api.types`. This file re-exports them with a
* stable public surface that the rest of the application relies on.
*/

import type {
QuestResponse,
SubmissionResponse,
UserStatsResponse,
} from '@/lib/types/api.types';

// ---------------------------------------------------------------------------
// Alias types (convenience re-maps of backend response shapes)
// ---------------------------------------------------------------------------

/** User statistics – mirrors the backend `UserStatsResponse`. */
export type UserStats = UserStatsResponse;

/** A quest item – mirrors the backend `QuestResponse`. */
export type Quest = QuestResponse;

/** A submission entry – mirrors the backend `SubmissionResponse`. */
export type Submission = SubmissionResponse;

// ---------------------------------------------------------------------------
// Domain types
// ---------------------------------------------------------------------------

/** A single earnings data-point used by the earnings chart. */
export interface EarningsData {
date: string;
amount: number;
}

/** A badge earned by the user. */
export interface Badge {
id: string;
name: string;
description: string;
icon: string;
earnedAt: string;
rarity: 'common' | 'rare' | 'epic' | 'legendary';
}

// ---------------------------------------------------------------------------
// Dashboard aggregate
// ---------------------------------------------------------------------------

/**
* Complete payload returned by `fetchDashboardData()` when called
* without an explicit Stellar address (i.e. mock / legacy mode).
*/
export interface DashboardData {
stats: UserStats;
activeQuests: Quest[];
recentSubmissions: Submission[];
earningsHistory: EarningsData[];
badges: Badge[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,19 @@ import {
withRetry,
createCancelToken,
type CancelToken,
} from './client';
} from '../client';
import type {
UserResponse,
UserStatsResponse,
UpdateProfileRequest,
UserSearchParams,
PaginationParams,
} from '@/lib/types/api.types';

import type { QuestResponse, SubmissionResponse } from '@/lib/types/api.types';

// Re-export legacy dashboard types for backward compat
export type {
UserStats,
Quest,
Submission,
EarningsData,
Badge,
DashboardData,
} from '../types/dashboard';
import type { EarningsData, Badge, DashboardData } from '../types/dashboard';
// (canonical definitions now live in ./types.ts; re-exported via barrel)
import type { EarningsData, Badge, DashboardData } from './types';

const dashboardDelay = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
Expand Down
6 changes: 3 additions & 3 deletions FrontEnd/my-app/lib/hooks/useUserStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import type {
Submission,
EarningsData,
Badge,
} from '../types/dashboard';
} from '@/lib/api/user';
import {
fetchUserStats,
fetchActiveQuests,
fetchRecentSubmissions,
fetchEarningsHistory,
fetchBadges,
fetchDashboardData,
} from '../api/user';
} from '@/lib/api/user';
import { useAuth } from '@/context/AuthContext';

interface UseUserStatsReturn {
Expand Down Expand Up @@ -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]);
Expand Down
Loading
Loading