Skip to main content
The frontend is a Next.js application in the frontend/ directory. It uses the App Router with route groups to separate unauthenticated and authenticated layouts, and communicates with the Express API through a typed services layer.

Tech stack

TechnologyVersionRole
Next.js15React framework / App Router
React19UI rendering
Tailwind CSS3Utility-first styling
shadcn/uiOwned component library
Zustand5Client-side state management
AxiosHTTP client
ZodSchema validation
React Hook FormForm state and submission

Source directory structure

frontend/src/
├── app/
│   ├── layout.js              # Root layout
│   ├── page.jsx               # Root redirect
│   ├── globals.css
│   ├── (auth)/
│   │   ├── layout.js          # Auth layout (centered card, no sidebar)
│   │   ├── login/
│   │   └── register/
│   └── (dashboard)/
│       ├── layout.js          # Dashboard layout (sidebar + topbar)
│       ├── dashboard/
│       ├── projects/
│       ├── tasks/
│       ├── users/
│       └── admin/
├── components/
│   ├── ui/                    # shadcn/ui primitives (Button, Input, Dialog…)
│   ├── sidebar/
│   ├── Proyectos/
│   ├── TareaCard/
│   └── ColumnasTareas/
├── hooks/
│   ├── useProyectos.js
│   ├── useTareas.js
│   ├── useAllTareas.js
│   ├── useUsuarios.js
│   └── use-mobile.js
├── services/
│   ├── auth.services.js
│   ├── proyectos.service.js
│   ├── tareas.service.js
│   ├── usuarios.service.js
│   └── admin.service.js
├── store/
│   └── authStore.js
└── lib/
    └── axios.js               # Axios instance with baseURL and auth interceptor

App Router layout

Next.js route groups (folders wrapped in parentheses) split the application into two layout trees without affecting the URL structure.

(auth) group

Contains /login and /register. The (auth)/layout.js renders a minimal centered layout with no navigation chrome. Unauthenticated users land here.

(dashboard) group

Contains all protected pages: /dashboard, /projects, /tasks, /users, and /admin. The (dashboard)/layout.js renders the persistent sidebar and top bar. Pages in this group read authentication state from the Zustand store and redirect to /login if isLogged is false.
Route groups are a Next.js convention. The parentheses in (auth) and (dashboard) are not part of the URL — /login and /dashboard are the actual paths.

Services layer

Every API resource has a dedicated service file in src/services/. Each file exports async functions that call the shared Axios instance. No page or component calls Axios directly.
import api from '@/lib/axios'

export const loginService = async (data) => {
  const res = await api.post('/login', data)
  return res.data
}

export const registerService = async (data) => {
  const res = await api.post('/register', data)
  return res.data
}
The five service files map directly to backend modules:
Service fileBackend moduleResponsible for
auth.services.jsauth/Login and registration
proyectos.service.jsproyectos/Project CRUD + task creation
tareas.service.jstareas/Task operations for assigned users
usuarios.service.jsusuarios/User search and management
admin.service.jstareas/ (admin endpoints)Admin-scoped task operations

Custom hooks

Data-fetching logic lives in src/hooks/ rather than directly in page components. Each hook wraps one or more service calls, manages loading and error state, and returns a refetch function to trigger revalidation.
// hooks/useProyectos.js
export const useProyectos = () => {
  const rol = useAuthStore((state) => state.usuario?.rol)

  const fetchProyectos = useCallback(async () => {
    const res = rol === 3
      ? await getProyectosService()      // admin: all projects
      : await getMyProyectosService()    // PM: only owned projects
    setProyectos(res.data)
  }, [rol])

  return { proyectos, loading, error, refetch: fetchProyectos }
}
Hooks read the user’s role from the Zustand store to decide which API endpoint to call, keeping role-conditional logic out of page components.

Zustand auth store

Client-side authentication state is managed in src/store/authStore.js using Zustand with the persist middleware.
export const useAuthStore = create(
  persist(
    (set) => ({
      usuario:  null,   // { id, nombre, email, rol }
      token:    null,   // JWT string
      isLogged: false,

      login: (usuario, token) => {
        localStorage.setItem('token', token)
        set({ usuario, token, isLogged: true })
      },

      logout: () => {
        localStorage.removeItem('token')
        set({ usuario: null, token: null, isLogged: false })
      }
    }),
    { name: 'auth-storage' }  // localStorage key
  )
)
The store persists to localStorage under the key auth-storage, so the session survives a page refresh. The Axios instance reads token from the store (or directly from localStorage) to attach the Authorization header on every request.
The token is stored in localStorage. This is acceptable for the current scope but is susceptible to XSS. A hardened deployment should move to httpOnly cookies.

Zod validation

Each form uses a Zod schema to validate input before submission. The schemas mirror the validation rules enforced by the backend validators, so error feedback is consistent whether the error is caught client-side or returned by the API. Zod schemas are used with React Hook Form’s zodResolver, which integrates schema validation into the form’s submit and change handlers and surfaces field-level errors in real time.

shadcn/ui components

UI primitives (Button, Input, Dialog, Select, etc.) live in src/components/ui/. These files are part of the project — they are not an external dependency that is imported from node_modules. This means:
  • Components can be modified freely without a library upgrade.
  • There is no version mismatch between the component source and the installed package.
  • New primitives can be added using the shadcn CLI (npx shadcn add <component>), which copies the source into src/components/ui/.
Higher-level feature components (Proyectos/, TareaCard/, ColumnasTareas/, sidebar/) compose the shadcn primitives.

Architecture overview

How the frontend and backend fit together, CORS setup, and the authentication flow.

Backend architecture

Domain-driven module structure, middleware pipeline, Knex.js migrations, and logging.

Build docs developers (and LLMs) love