feat: implement 8 features across full stack
- Leaderboard: rank-based percentile tiers (champion/gold/silver/bronze),
deterministic tie-breaking (xp DESC, user_id ASC)
- Mentor chat: md: breakpoints, safe-area-inset-bottom padding
- Learning progress: server-side progressPct, curriculum expansion safety
- Physical orders: hard-disabled with 410 Gone
- Certificate approval: pending/issued flow, admin issue/revoke endpoints,
certificate_requires_approval setting (migration 0031)
- Draft/publish: is_published on lessons and chapters, admin publish UI
(migration 0030)
- Video panel: static thumbnails (no preload), client-side pagination
- Rich editor: TipTap + tiptap-markdown for lesson descriptions,
marked + DOMPurify for SafeMarkdown rendering
This commit is contained in:
@@ -33,10 +33,11 @@ export interface User {
|
||||
export interface Certificate {
|
||||
id: number;
|
||||
userId: number;
|
||||
publicId: string;
|
||||
publicId?: string;
|
||||
fullName: string;
|
||||
issuedAt: string;
|
||||
revokedAt?: string;
|
||||
status: "pending" | "issued";
|
||||
}
|
||||
|
||||
export type PhysicalOrderStatus = "requested" | "paid" | "shipped" | "cancelled";
|
||||
@@ -91,6 +92,7 @@ export interface Lesson {
|
||||
sortOrder: number;
|
||||
xpReward: number;
|
||||
isActive: boolean;
|
||||
isPublished: boolean;
|
||||
}
|
||||
|
||||
export interface Chapter {
|
||||
@@ -99,6 +101,7 @@ export interface Chapter {
|
||||
description: string;
|
||||
icon: string;
|
||||
sortOrder: number;
|
||||
isPublished: boolean;
|
||||
}
|
||||
|
||||
export interface TreeLesson {
|
||||
@@ -122,6 +125,9 @@ export interface TreeChapter extends Chapter {
|
||||
|
||||
export interface TreeData {
|
||||
chapters: TreeChapter[];
|
||||
totalLessons: number;
|
||||
completedLessons: number;
|
||||
progressPct: number;
|
||||
me: { xp: number; streak: number; hearts: number; isLocked: boolean; role: Role };
|
||||
}
|
||||
|
||||
@@ -246,12 +252,15 @@ export interface Conversation {
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
export type LeaderboardTier = "champion" | "gold" | "silver" | "bronze";
|
||||
|
||||
export interface LeaderboardEntry {
|
||||
userId: number;
|
||||
name: string;
|
||||
email: string;
|
||||
xp: number;
|
||||
rank: number;
|
||||
tier: LeaderboardTier;
|
||||
avatarVariant?: "marble" | "beam" | "pixel" | "sunset" | "ring" | "bauhaus";
|
||||
avatarPalette?: string;
|
||||
avatarPhoto?: string;
|
||||
@@ -260,7 +269,8 @@ export interface LeaderboardEntry {
|
||||
export interface LeaderboardData {
|
||||
week: string;
|
||||
entries: LeaderboardEntry[];
|
||||
me: { rank: number; xp: number; streak: number };
|
||||
totalParticipants: number;
|
||||
me: { rank: number; xp: number; streak: number; tier: LeaderboardTier };
|
||||
}
|
||||
|
||||
export interface NotificationPrefs {
|
||||
|
||||
Reference in New Issue
Block a user