United States/Tennessee
BlogJanuary 7, 2026

Priceless learning and extremely satisfied code quality. It's all about consistency.

Doruk Kocausta
Priceless learning and extremely satisfied code quality. It's all about consistency.
Hello everyone! Last time I shared an update about the MyFinance project, my frontend design was ready for the logic part. A lot has changed since then, so here’s a deep dive into what’s new, what I had to figure out, and how everything connects. user.ts types
user.ts data
AuthContex.tsx
ModalContext.tsx
ThemeContext.tsx
AppModals.tsx
I needed a way to keep user information accessible everywhere in the app. But I wanted more than just storing user info—I wanted to manage login, register, and user updates all together, so I could easily use them anywhere in my application. That’s how AuthContext came to life. I also realized I needed a modal that could handle any user feedback, not just special cases. My last project (MealMatch) taught me a lesson: creating a bunch of different modals leads to headaches. So, I wanted a solution that could handle all kinds of content and actions in one reusable modal. And that’s how AppModal was born. Once I began thinking about information modals, I understood that I needed a ModalContext to control and display these modals with different content and buttons—sometimes confirm/cancel, sometimes just OK. ModalContext solved this elegantly. I wanted a simple way to add dark/light mode to the UI, so ThemeContext became the solution for toggling themes with one button anywhere in the site. To make all this work, I needed a sample user and a clear definition for what a user object looks like. So I created data/user.ts and types/user.ts.
Typescript
export interface User {
  id: string;
  username: string;
  email: string;
  monthlyCircleDate: string;
  hashedPassword: string;
}
This file defines what a user looks like in my app—with id, username, email, monthlyCircleDate (for tracking), and the hashed password (very important for authentication).
Typescript
import { User } from '@/types/user';

export const mockUser: User[] = [
  {
    id: 'u1',
    username: 'kedycat',
    email: 'kat@example.com',
    monthlyCircleDate: '2024-01-01',
    hashedPassword: '$2b$12$fakeBcryptHashForNow',
  },
];
Why do I need this?
Since I don’t have a backend yet, I use mockUser to simulate a real user—makes development smoother and lets me test login/register logic.
Typescript
'use client'
import React, { createContext, useContext, useState, ReactNode } from 'react'
import { User } from '@/types/user'
import { mockUser } from '@/data/user'

type AuthContextType = {
  currentUser: User | null
  login: (username: string, password: string) => { success: boolean; message: string }
  logout: () => void
  register: (user: Omit<User, 'id' | 'hashedPassword'> & { password: string }) => {
    success: boolean
    message: string
  }
  isAuthenticated: boolean
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function AuthProvider({ children }: { children: ReactNode }) {
  const [users, setUsers] = useState<User[]>(mockUser)
  const [currentUser, setCurrentUser] = useState<User | null>(null)

  // Login checks for a matching username and password in the mock users array.
  function login(username: string, password: string) {
    const user = users.find(u => u.username === username)
    if (!user) {
      return { success: false, message: 'User not found' }
    }
    // WARNING: This "password check" is just for the MVP. Real apps should always hash+salt and check server side!
    if (user.hashedPassword !== password) {
      return { success: false, message: 'Login failed. Incorrect password.' }
    }
    setCurrentUser(user)
    return { success: true, message: 'Login successful.' }
  }

  // Logout wipes out the current user (logs out).
  function logout() {
    setCurrentUser(null)
  }

  // Register adds a new user, making sure the username and email are unique.
  function register(newUser: Omit<User, 'id' | 'hashedPassword'> & { password: string }) {
    if (users.some(u => u.username === newUser.username || u.email === newUser.email)) {
      return { success: false, message: 'Username or email already exists.' }
    }
    const fakeHashed = newUser.password // For demo only!
    const user: User = {
      ...newUser,
      id: 'user' + (users.length + 1),
      hashedPassword: fakeHashed,
    }
    setUsers([...users, user])
    setCurrentUser(user)
    return { success: true, message: 'Registration successful' }
  }

  return (
    <AuthContext.Provider
      value={{
        currentUser,
        login,
        logout,
        register,
        isAuthenticated: !!currentUser,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

// useAuth provides an easy way for any component to access login, logout, register, and user info.
export function useAuth() {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth must be used under AuthProvider')
  return ctx
}
Explanation:
AuthProvider wraps the app and lets me read or write user info from anywhere! Login, logout, and register become simple function calls. The user data lives in context, so I won’t need to pass it around with props.
For now, it uses mock data and fake password checking—real authentication always belongs on the server!
Typescript
'use client'
import { createContext, useContext, useState, ReactNode } from 'react'

type ModalState = {
  isOpen: boolean
  message: string
  onConfirm?: () => void
  onCancel?: () => void
  showConfirm?: boolean
}

type ModalContextType = {
  modal: ModalState
  showModal: (message: string) => void
  showConfirmModal: (message: string, onConfirm: () => void, onCancel: () => void) => void
  closeModal: () => void
}

const ModalContext = createContext<ModalContextType | undefined>(undefined)

export function ModalProvider({ children }: { children: ReactNode }) {
  const [modal, setModal] = useState<ModalState>({
    isOpen: false,
    message: '',
    showConfirm: false,
  })

  // showModal opens a regular message-only modal.
  function showModal(message: string) {
    setModal({ isOpen: true, message, showConfirm: false })
  }
  // showConfirmModal opens a modal with confirm/cancel buttons and custom handlers.
  function showConfirmModal(message: string, onConfirm: () => void, onCancel: () => void) {
    setModal({ isOpen: true, message, showConfirm: true, onConfirm, onCancel })
  }
  // closeModal closes the modal.
  function closeModal() {
    setModal({ ...modal, isOpen: false })
  }
  return (
    <ModalContext.Provider value={{ modal, showModal, showConfirmModal, closeModal }}>
      {children}
    </ModalContext.Provider>
  )
}

export function useModal() {
  const ctx = useContext(ModalContext)
  if (!ctx) throw new Error('useModal must be used inside ModalProvider')
  return ctx
}
Explanation:
ModalProvider lets me show modals from anywhere with functions: showModal for info modals, showConfirmModal for confirm/cancel modals.
Modal state and controls live in context, so UI stays organized and powerful.
Typescript
'use client'
import { useModal } from '@/context/ModalContext'

export default function AppModal() {
  const { modal, closeModal } = useModal()

  if (!modal.isOpen) return null

  // Shows the modal UI when isOpen is true.
  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
      <div className="bg-[#989899] rounded p-6 shadow-lg min-w-[250px] text-center">
        <p className="mb-4">{modal.message}</p>
        {modal.showConfirm ? (
          <div className="flex gap-3 justify-center">
            <button
              className="bg-red-600 text-white p-2 rounded"
              onClick={() => {
                modal.onCancel?.()
                closeModal()
              }}>
              Cancel
            </button>
            <button
              className="bg-[#29388A]-600 text-white p-2 rounded"
              onClick={() => {
                modal.onConfirm?.()
                closeModal()
              }}>
              Confirm
            </button>
          </div>
        ) : (
          <button className="bg-[#29388A] text-white p-2 rounded w-full" onClick={closeModal}>
            OK
          </button>
        )}
      </div>
    </div>
  )
}
Explanation:
AppModal listens to the modal context and renders a visually appealing popup whenever modal.isOpen is true.
It automatically offers confirm/cancel buttons if it’s a confirm modal, or just OK otherwise.
Thanks to ModalContext, I never need dozens of different modal components—just this one does it all!
Typescript
'use client'
import { createContext, useContext, useState, ReactNode } from 'react'

type Theme = 'light' | 'dark'
type ThemeContextType = {
  theme: Theme
  setTheme: (t: Theme) => void
  toggleTheme: () => void
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined)

export function ThemeProvider({ children }: { children: ReactNode }): JSX.Element {
  const [theme, setTheme] = useState<Theme>('light')

  function toggleTheme() {
    setTheme(t => (t === 'light' ? 'dark' : 'light'))
  }
  return (
    <ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  const ctx = useContext(ThemeContext)
  if (!ctx) throw new Error('useTheme must be used inside ThemeProvider')
  return ctx
}
Explanation:
ThemeProvider tracks the current theme (light or dark) and lets me swap modes from anywhere in the app.
Want to add a dark mode toggle? Just call toggleTheme wherever I want the button to live.
Right now, this just changes a value—it’s not wired up to real dark mode styling yet since I still need to set up the CSS logic for dark colors.
  • I wrap my pages in the context providers (AuthProvider, ModalProvider, ThemeProvider) at the top level.
  • Any component can useAuth, useModal, or useTheme to get or set user, modal, or theme info.
  • When user interacts:
    • Login or register: useAuth().login() or register() is called, state updates, and AuthContext lets all components “know” the current user.
    • Show feedback: Call useModal().showModal() or showConfirmModal() to open a popup instantly. AppModal listens, shows the modal, and handles user actions.
    • Toggle UI mode: Call useTheme().toggleTheme() to switch between light and dark mode everywhere.
  • As these contexts update, my UI updates reactively and I never need to prop-drill or duplicate logic.
My next step is to build more contexts for the tons of data I need to show and edit throughout MyFinance—like incomes, outcomes, investments, and user profile. And I’ll be tackling more edit modals to make user data interactive and editable everywhere. I’m genuinely excited to dive deep into SQL soon. This is going to level up the whole project. So I’ll keep learning and building, constantly improving my skills and sharing the journey. Thank you so much for reading and joining me in this process!
If you have advice, questions, or just want to say hi, leave a comment on my linkedin post, always appreciated.
Stay consistent, keep building, and have a wonderful day.
Share this post:
On this page