Skip to content

Commit

Permalink
chore: refactor types declaration and fix some bugs (#22)
Browse files Browse the repository at this point in the history
- Updated jsconfig.json for enhanced type consistency
- Fix non-existent variable in Header and Home.test thanks to types
- Add type declaration for UserContext, calculateProgress, authUtils,
createUserProfile, firebaseConfig, streakUtils, and theme
- Add auto-eslint and indent 2 spaces in .vscode/settings.json
  • Loading branch information
ZL-Asica authored Nov 7, 2024
1 parent 5371466 commit 2cf1cab
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 39 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.organizeImports": "always"
"source.organizeImports": "always",
"source.fixAll.eslint": "always",
"source.addMissingImports.ts": "always"
},
"search.exclude": {
"**/.yarn": true,
Expand Down
9 changes: 8 additions & 1 deletion jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
{
"compilerOptions": {
"skipLibCheck": true,
"target": "es6",
"moduleResolution": "node",
"jsx": "react-jsx",
"baseUrl": ".",
"allowJs": true,
"paths": {
"@/*": ["src/*"]
}
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
6 changes: 5 additions & 1 deletion src/components/Home/DeleteItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import DeleteIcon from '@mui/icons-material/Delete'
import { IconButton, Tooltip } from '@mui/material'
import { useState } from 'react'

const DeleteItem = ({ goalIndex, microGoalIndex, taskIndex }) => {
const DeleteItem = ({
goalIndex,
microGoalIndex = undefined,
taskIndex = undefined,
}) => {
const { deleteItem } = useGoalsUpdater()
const [isDialogOpen, setIsDialogOpen] = useState(false)

Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const Header = () => {
color='inherit'
onClick={() => setOpenConfirmDialog(true)}
>
<Avatar alt={user.displayName} src={user.photoURL} />
<Avatar alt={user.name} src={user.profilePic} />
</IconButton>

{/* Dialog for Confirm Sign Out */}
Expand Down
82 changes: 79 additions & 3 deletions src/contexts/UserContext.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-check

import LoadingCircle from '@/components/common/LoadingCircle'
import { signInWithGoogle } from '@/utils/firebase/authUtils'
import {
Expand All @@ -8,17 +10,87 @@ import { auth } from '@/utils/firebaseConfig'
import { onAuthStateChanged, signOut } from 'firebase/auth'
import { createContext, useContext, useEffect, useState } from 'react'

/**
* @typedef {Object} Task
* @property {string} name - Name of the task.
* @property {boolean} completed - Whether the task is completed.
*/

/**
* @typedef {Object<string, Task>} TaskMap - A map of task IDs to Task objects.
*/

/**
* @typedef {Object} MicroGoal
* @property {string} name - Name of the microgoal.
* @property {boolean} expanded - Whether the microgoal is expanded.
* @property {TaskMap} tasks - Map of task IDs to tasks within the microgoal.
*/

/**
* @typedef {Object<string, MicroGoal>} MicroGoalMap - A map of microgoal IDs to MicroGoal objects.
*/

/**
* @typedef {Object} Goal
* @property {string} name - Name of the goal.
* @property {boolean} expanded - Whether the goal is expanded.
* @property {string} category - HEX color code for the goal category.
* @property {MicroGoalMap} microgoals - Map of microgoal IDs to microgoals within the goal.
*/

/**
* @typedef {Object<string, Goal>} GoalMap - A map of goal IDs to Goal objects.
*/

/**
* @typedef {Object} Streak
* @property {Object.<string, number>} completedDays - Map of dates (as strings) to their completion counts.
* @property {number} count - Current streak count.
*/

/**
* @typedef {Object} User
* @property {string} uid - User's UID.
* @property {string} profilePic - URL of the user's profile picture.
* @property {string} name - User's display name.
* @property {GoalMap} goals - Map of goal IDs to user's goals.
* @property {Streak} streak - User's streak data.
*/

/**
* @typedef {Object} UserContextType
* @property {User | null} user - Current user profile or null if not logged in.
* @property {boolean} loading - Whether the user data is still loading.
* @property {() => Promise<boolean>} handleSignIn - Function to handle user sign-in.
* @property {() => Promise<void>} handleSignOut - Function to handle user sign-out.
* @property {(updates: Partial<User>) => Promise<void>} updateProfile - Function to update user profile.
*/

// Create UserContext
const UserContext = createContext()
/** @type {import('react').Context<UserContextType>} */
const UserContext = createContext({
user: null,
loading: false,
handleSignIn: async () => false,
handleSignOut: async () => {},
updateProfile: async (updates) => {},
})

// Custom hook to use UserContext
/** @returns {UserContextType} */
export const useUser = () => useContext(UserContext)

/**
* @param {{ children: React.ReactNode }} props - The component's props.
* @returns {JSX.Element} - The UserProvider component.
*/
export const UserProvider = ({ children }) => {
const [user, setUser] = useState(null)
const [user, setUser] = useState(/** @type {User | null} */ (null))
const [loading, setLoading] = useState(true)

// handle Sign-In
/** @returns {Promise<boolean>} */
const handleSignIn = async () => {
const userData = await signInWithGoogle()
if (userData) {
Expand All @@ -29,6 +101,7 @@ export const UserProvider = ({ children }) => {
}

// Handle Sign-Out
/** @returns {Promise<void>} */
const handleSignOut = async () => {
try {
await signOut(auth)
Expand All @@ -43,7 +116,6 @@ export const UserProvider = ({ children }) => {
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (firebaseUser) => {
if (firebaseUser) {
// Fetch user profile if user is logged in
const profile = await fetchUserProfile(firebaseUser.uid)
if (profile) {
setUser({ ...firebaseUser, ...profile })
Expand All @@ -61,6 +133,10 @@ export const UserProvider = ({ children }) => {
}, [])

// Function to update user profile
/**
* @param {Partial<User>} updates - The updates to apply to the user profile.
* @returns {Promise<void>}
*/
const updateProfile = async (updates) => {
if (user) {
await updateUserProfile(user.uid, updates)
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home.test.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UserProvider } from '@/contexts/UserContext'
import Home from '@/pages/Home'
import { act, render, screen } from '@testing-library/react'
import { describe, expect, test, vi } from 'vitest'
import { beforeEach, describe, expect, test, vi } from 'vitest'

// Mock `@contexts/UserContext` to control user profile and updates
vi.mock('@/contexts/UserContext', async () => {
Expand Down
7 changes: 7 additions & 0 deletions src/utils/calculateProgress.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// @ts-check

/**
* Calculates the progress of the goal tracker.
* @param {Array<object>} items - The items to calculate progress for.
* @returns {number} - The progress percentage.
*/
export const calculateProgress = (items) => {
// Calculate the progress of the goal tracker
const completed = items.reduce(
Expand Down
7 changes: 6 additions & 1 deletion src/utils/firebase/authUtils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-check

import {
createFirstUserProfile,
fetchUserProfile,
Expand All @@ -7,7 +9,10 @@ import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'

const provider = new GoogleAuthProvider()

// Google Sign-In function
/**
* Signs in the user using Google OAuth
* @returns {Promise<object | null>} - The user profile data or null if sign-in fails.
*/
export const signInWithGoogle = async () => {
try {
const result = await signInWithPopup(auth, provider)
Expand Down
10 changes: 6 additions & 4 deletions src/utils/firebase/createUserProfile.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// @ts-check

import { db } from '@/utils/firebaseConfig'
import { doc, getDoc, setDoc, updateDoc } from 'firebase/firestore'

/**
* Fetches the user profile data from Firestore by UID.
* @param {string} uid - User's UID.
* @returns {object|null} - The user profile data or null if not found.
* @returns {Promise<object|null>} - The user profile data or null if not found.
*/
export const fetchUserProfile = async (uid) => {
try {
Expand All @@ -26,7 +28,7 @@ export const fetchUserProfile = async (uid) => {
/**
* Creates a user profile in Firestore with default data if it doesn't exist.
* @param {object} user - The authenticated user object.
* @returns {boolean} - Returns true if the profile is created or exists, false otherwise.
* @returns {Promise<boolean>} - Returns true if the profile is created, false otherwise.
*/
export const createFirstUserProfile = async (user) => {
const { uid, photoURL, displayName } = user
Expand Down Expand Up @@ -60,7 +62,7 @@ export const createFirstUserProfile = async (user) => {
/**
* Retrieves the user profile from Firestore by UID.
* @param {string} uid - User's UID.
* @returns {object|null} - The user profile data or null if not found.
* @returns {Promise<object|null>} - The user profile data or null if not found.
*/
export const getUserProfile = async (uid) => {
return await fetchUserProfile(uid)
Expand All @@ -70,7 +72,7 @@ export const getUserProfile = async (uid) => {
* Updates the user profile data in Firestore by UID.
* @param {string} uid - User's UID.
* @param {object} updates - The fields to update in the user profile.
* @returns {boolean} - Returns true if the update is successful, false otherwise.
* @returns {Promise<boolean>} - Returns true if the profile is updated, false otherwise.
*/
export const updateUserProfile = async (uid, updates) => {
try {
Expand Down
17 changes: 4 additions & 13 deletions src/utils/firebaseConfig.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// @ts-check

// Import the functions you need from the SDKs you need
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import {
CACHE_SIZE_UNLIMITED,
initializeFirestore,
persistentLocalCache,
persistentSingleTabManager,
} from 'firebase/firestore'
import { initializeFirestore } from 'firebase/firestore'
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

Expand All @@ -25,12 +22,6 @@ const firebaseConfig = {
// Initialize Firebase
const app = initializeApp(firebaseConfig)

// Enable IndexedDB persistence with single-tab manager
export const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentSingleTabManager(),
cacheSizeBytes: CACHE_SIZE_UNLIMITED,
}),
})
export const db = initializeFirestore(app, {})

export const auth = getAuth(app)
49 changes: 37 additions & 12 deletions src/utils/streakUtils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// @ts-check

/**
* Gets the current date in Chicago timezone (formatted as YYYY-MM-DD).
* @returns {string} The current Chicago date as a string in YYYY-MM-DD format
*/
export const getChicagoDate = () => {
const chicagoTimeOffset = -6 * 60 // CST is UTC-6
const chicagoDate = new Date(
Expand All @@ -6,31 +12,50 @@ export const getChicagoDate = () => {
return chicagoDate.toISOString().split('T')[0]
}

// Function to update the streak count and completed days for a user
/**
* Streak data type for a user.
* @typedef {Object} Streak
* @property {Object.<string, number>} completedDays - Map of date strings to their completion counts.
* @property {number} count - Current streak count.
*/

/**
* User data type.
* @typedef {Object} User
* @property {Streak} streak - User's streak data.
*/

/**
* Updates the streak count and completed days for a user.
* @param {User} user - The user object containing streak information.
* @param {number} countChange - The change to apply to the completion count for the current date.
* @returns {{completedDays: Object.<string, number>, count: number}} - Updated completedDays as a map of date-count pairs and the new streak count.
*/
export const updateStreakDays = (user, countChange) => {
const currentDate = getChicagoDate()

// {date: count, ...}
// { completedDays: { '2024-11-01': 1, '2024-11-02': 0 }, count: 1 }
const completedDays = user.streak?.completedDays || {}
// Initial streak count if not set yet
const streakCount = user.streak?.count || 0
const count = user.streak?.count || 0

// Update the completed days count
// Get the current count for the current date or initialize it to 0
const currentCount = completedDays[currentDate] || 0

// Update the count for the current date
completedDays[currentDate] = Math.max(0, currentCount + countChange)

// Check if the streak count needs to be updated
let newStreakCount = streakCount
// Adjust the streak count based on changes to the current day's count
let newCount = count
if (currentCount === 0 && countChange > 0) {
// 0 -> positive, streak count increases
newStreakCount++
// Increment streak if new positive count for the day
newCount++
} else if (currentCount > 0 && completedDays[currentDate] === 0) {
// positive -> 0, streak count decreases
newStreakCount = Math.max(0, newStreakCount - 1)
// Decrement streak if current day count goes to 0
newCount = Math.max(0, newCount - 1)
}

return {
completedDays,
count: newStreakCount,
count: newCount,
}
}
4 changes: 3 additions & 1 deletion src/utils/theme.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-check

import { createTheme } from '@mui/material/styles'

export const theme = createTheme({
Expand All @@ -6,7 +8,7 @@ export const theme = createTheme({
light: '#E4E0EE',
main: '#4E2A84',
dark: '#361d5c',
contractText: '#fff',
contrastText: '#fff',
},
secondary: {
main: '#f44336',
Expand Down

0 comments on commit 2cf1cab

Please sign in to comment.