diff --git a/src/components/StreakView/User/User.tsx b/src/components/StreakView/User/User.tsx index e3364ae..b2b5d37 100644 --- a/src/components/StreakView/User/User.tsx +++ b/src/components/StreakView/User/User.tsx @@ -1,4 +1,4 @@ -import { StreakData, useUserData } from "../../../hooks/useUserData"; +import { useUserData } from "../../../hooks/useUserData"; import { proxyify } from "../../../utilities"; import { FireBorder } from "./FireBorder/FireBorder"; import "./User.less"; @@ -17,39 +17,22 @@ export const User = (props: UserProps) => { return Failed to retrieve streak data...; } - const didLessonToday = didALessonToday(user.streakData); - return ( - +

{user.name}

- ({user.courses[0]?.title}) + ({user.currentCourse?.title}) {user.name} - +
); }; -/** - * Checks if a lesson was done today based on the streak information. - * @param streakInfo - The streak information object. - * @returns A boolean indicating whether a lesson was done today. - */ -const didALessonToday = (streakData: StreakData): boolean => { - const streakInfo = streakData.currentStreak; - if (streakInfo) { - return ( - new Date(streakInfo.endDate).getTime() >= new Date().setHours(0, 0, 0, 0) - ); - } - return false; -}; - type StreakBlockProps = { streak: number; }; diff --git a/src/entities/config.ts b/src/entities/models/config.ts similarity index 100% rename from src/entities/config.ts rename to src/entities/models/config.ts diff --git a/src/entities/models/index.ts b/src/entities/models/index.ts new file mode 100644 index 0000000..85ee916 --- /dev/null +++ b/src/entities/models/index.ts @@ -0,0 +1,2 @@ +export * from "./config"; +export * from "./user"; diff --git a/src/entities/models/user.ts b/src/entities/models/user.ts new file mode 100644 index 0000000..9b0a6e6 --- /dev/null +++ b/src/entities/models/user.ts @@ -0,0 +1,75 @@ +import { CourseDto, StreakDataDto, UserDto } from "../responses"; + +export class User { + public readonly name: string; + public readonly username: string; + public readonly pictureUrl: string; + public readonly streak: Streak; + public readonly currentCourse?: CurrentCourse; + + public static fromResponse(response: UserDto): User { + return new User(response); + } + + private constructor(user: UserDto) { + this.name = user.name; + this.username = user.username; + this.pictureUrl = user.picture.replace("//", "https://") + "/xxlarge"; + this.streak = Streak.fromResponse(user.streakData); + const currentCourse = user.courses.find( + (c) => c.id === user.currentCourseId, + ); + if (currentCourse) { + this.currentCourse = CurrentCourse.fromResponse(currentCourse); + } + } +} + +class Streak { + public readonly days: number = 0; + public readonly startDate?: string; + public readonly endDate?: string; + + public static fromResponse(response: StreakDataDto): Streak { + if (!response.currentStreak) { + return new Streak(0); + } + return new Streak( + response.currentStreak.length, + response.currentStreak.startDate, + response.currentStreak.endDate, + ); + } + + private constructor(days: number, startDate?: string, endDate?: string) { + this.days = days; + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Returns whether the user did a lesson today. + */ + public didALessonToday(): boolean { + if (this.endDate) { + return ( + new Date(this.endDate).getTime() >= new Date().setHours(0, 0, 0, 0) + ); + } + return false; + } +} + +class CurrentCourse { + public readonly title: string; + public readonly languageCode: string; + + public static fromResponse(response: CourseDto): CurrentCourse { + return new CurrentCourse(response); + } + + private constructor(course: CourseDto) { + this.title = course.title; + this.languageCode = course.learningLanguage; + } +} diff --git a/src/entities/responses/duolingo-api-response.ts b/src/entities/responses/duolingo-api-response.ts new file mode 100644 index 0000000..e517266 --- /dev/null +++ b/src/entities/responses/duolingo-api-response.ts @@ -0,0 +1,65 @@ +export type DuolingoResponse = { + users: UserDto[]; +}; + +export type UserDto = { + joinedClassroomIds: unknown[]; + streak: number; + motivation: string; + acquisitionSurveyReason: string; + shouldForceConnectPhoneNumber: boolean; + picture: string; + learningLanguage: string; + hasFacebookId: boolean; + shakeToReportEnabled: null | boolean; + liveOpsFeatures: unknown[]; + canUseModerationTools: boolean; + id: number; + betaStatus: string; + hasGoogleId: boolean; + privacySettings: string[]; + fromLanguage: string; + hasRecentActivity15: boolean; + _achievements: unknown[]; + observedClassroomIds: unknown[]; + username: string; + bio: string; + profileCountry: null | string; + chinaUserModerationRecords: unknown[]; + globalAmbassadorStatus: unknown; + currentCourseId: string; + hasPhoneNumber: boolean; + creationDate: number; + achievements: unknown[]; + hasPlus: boolean; + name: string; + roles: string[]; + classroomLeaderboardsEnabled: boolean; + emailVerified: boolean; + courses: CourseDto[]; + totalXp: number; + streakData: StreakDataDto; +}; + +export type CourseDto = { + preload: boolean; + placementTestAvailable: boolean; + authorId: string; + title: string; + learningLanguage: string; + xp: number; + healthEnabled: boolean; + fromLanguage: string; + crowns: number; + id: string; +}; + +export type StreakDataDto = { + currentStreak: StreakInfoDto | null; +}; + +export type StreakInfoDto = { + startDate: string; + length: number; + endDate: string; +}; diff --git a/src/entities/responses/index.ts b/src/entities/responses/index.ts new file mode 100644 index 0000000..ba551c9 --- /dev/null +++ b/src/entities/responses/index.ts @@ -0,0 +1 @@ +export * from "./duolingo-api-response"; diff --git a/src/hooks/useConfiguration.ts b/src/hooks/useConfiguration.ts index 9492524..6d8e979 100644 --- a/src/hooks/useConfiguration.ts +++ b/src/hooks/useConfiguration.ts @@ -1,5 +1,5 @@ import { useLocalStorage } from "usehooks-ts"; -import { Config } from "../entities/config"; +import { Config } from "../entities/models"; export function useConfiguration() { const [config, setConfig] = useLocalStorage("config", { diff --git a/src/hooks/useUserData.ts b/src/hooks/useUserData.ts index 82faa68..a59920b 100644 --- a/src/hooks/useUserData.ts +++ b/src/hooks/useUserData.ts @@ -1,5 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { fetchWithProxy } from "../utilities"; +import { DuolingoResponse } from "../entities/responses"; +import { User } from "../entities/models"; /** * Custom hook to fetch the user's information from Duolingo. @@ -23,73 +25,7 @@ export function useUserData(userName: string) { if (isError) console.error("Error fetching streak info", data); const isValid = !isError && data?.users?.length === 1; - const user = isValid ? data.users[0] : null; + const user = isValid ? User.fromResponse(data.users[0]) : null; return { isPending, isValid, user }; } - -type DuolingoResponse = { - users: User[]; -}; - -export type User = { - joinedClassroomIds: unknown[]; - streak: number; - motivation: string; - acquisitionSurveyReason: string; - shouldForceConnectPhoneNumber: boolean; - picture: string; - learningLanguage: string; - hasFacebookId: boolean; - shakeToReportEnabled: null | boolean; - liveOpsFeatures: unknown[]; - canUseModerationTools: boolean; - id: number; - betaStatus: string; - hasGoogleId: boolean; - privacySettings: string[]; - fromLanguage: string; - hasRecentActivity15: boolean; - _achievements: unknown[]; - observedClassroomIds: unknown[]; - username: string; - bio: string; - profileCountry: null | string; - chinaUserModerationRecords: unknown[]; - globalAmbassadorStatus: unknown; - currentCourseId: string; - hasPhoneNumber: boolean; - creationDate: number; - achievements: unknown[]; - hasPlus: boolean; - name: string; - roles: string[]; - classroomLeaderboardsEnabled: boolean; - emailVerified: boolean; - courses: Course[]; - totalXp: number; - streakData: StreakData; -}; - -type Course = { - preload: boolean; - placementTestAvailable: boolean; - authorId: string; - title: string; - learningLanguage: string; - xp: number; - healthEnabled: boolean; - fromLanguage: string; - crowns: number; - id: string; -}; - -export type StreakData = { - currentStreak: StreakInfo | null; -}; - -type StreakInfo = { - startDate: string; - length: number; - endDate: string; -};