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})
-
+
);
};
-/**
- * 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;
-};