Switch to minimal UI
jump to
@@ -76,7 +76,7 @@ // Exercise handlers (read-only)
func getExercisesHandler(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var exercises []database.Exercise - query := db.Preload("PrimaryMuscles").Preload("SecondaryMuscles") + query := db.Model(&database.Exercise{}) // Optional filtering if category := r.URL.Query().Get("category"); category != "" {@@ -104,7 +104,7 @@ return
} var exercise database.Exercise - if err := db.Preload("PrimaryMuscles").Preload("SecondaryMuscles").First(&exercise, id).Error; err != nil { + if err := db.First(&exercise, id).Error; err != nil { if err == gorm.ErrRecordNotFound { jsonError(w, http.StatusNotFound, "Exercise not found") return@@ -117,19 +117,6 @@ jsonResponse(w, http.StatusOK, exercise)
} } -// Muscle handlers (read-only) -func getMusclesHandler(db *gorm.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var muscles []database.Muscle - if err := db.Find(&muscles).Error; err != nil { - jsonError(w, http.StatusInternalServerError, "Database error") - return - } - - jsonResponse(w, http.StatusOK, muscles) - } -} - // Routine handlers func getRoutinesHandler(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) {@@ -152,8 +139,7 @@ return
} var routine database.Routine - if err := db.Preload("Items.ExerciseItems.Exercise.PrimaryMuscles"). - Preload("Items.ExerciseItems.Exercise.SecondaryMuscles"). + if err := db.Preload("Items.ExerciseItems.Exercise"). Preload("Items.ExerciseItems.Sets"). Order("Items.order_index, Items.ExerciseItems.order_index, Items.ExerciseItems.Sets.order_index"). First(&routine, id).Error; err != nil {@@ -260,8 +246,7 @@
var record database.RecordRoutine if err := db.Preload("Routine"). Preload("RecordItems.RoutineItem"). - Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise.PrimaryMuscles"). - Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise.SecondaryMuscles"). + Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise"). Preload("RecordItems.RecordExerciseItems.RecordSets.Set"). Order("RecordItems.order_index, RecordItems.RecordExerciseItems.order_index, RecordItems.RecordExerciseItems.RecordSets.order_index"). First(&record, id).Error; err != nil {
@@ -2,10 +2,9 @@ package api
import ( "net/http" - "os" - "path/filepath" "github.com/birabittoh/go-lift/src/database" + "github.com/birabittoh/go-lift/src/ui" ) const uiDir = "ui"@@ -29,9 +28,6 @@ // Exercises routes (read-only)
mux.HandleFunc("GET /api/exercises", getExercisesHandler(db)) mux.HandleFunc("GET /api/exercises/{id}", getExerciseHandler(db)) - // Muscles routes (read-only) - mux.HandleFunc("GET /api/muscles", getMusclesHandler(db)) - // Routines routes mux.HandleFunc("GET /api/routines", getRoutinesHandler(db)) mux.HandleFunc("GET /api/routines/{id}", getRoutineHandler(db))@@ -49,18 +45,21 @@
// Stats routes mux.HandleFunc("GET /api/stats", getStatsHandler(db)) - // Static UI route - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - requestedFile := filepath.Join(uiDir, r.URL.Path) - _, err := os.Stat(requestedFile) + /* + // Static UI route + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + requestedFile := filepath.Join(uiDir, r.URL.Path) + _, err := os.Stat(requestedFile) - if err == nil || r.URL.Path == "/" { - fileServer.ServeHTTP(w, r) - return - } + if err == nil || r.URL.Path == "/" { + fileServer.ServeHTTP(w, r) + return + } - http.ServeFile(w, r, filepath.Join(uiDir, "index.html")) - }) + http.ServeFile(w, r, filepath.Join(uiDir, "index.html")) + }) + */ + ui.InitServeMux(mux) return mux }
@@ -2,7 +2,6 @@ package database
import ( "encoding/json" - "errors" "fmt" "io" "log"@@ -26,64 +25,8 @@ Images []string `json:"images"`
ID string `json:"id"` } -// upsertLookupEntities creates or updates all lookup table entries and returns lookup maps -func (db *Database) upsertLookupEntities(exercises []importedExercise) (map[string]uint, error) { - // Collect unique values - uniqueValues := make(map[string]bool) - - // Extract unique values from exercises - for _, exercise := range exercises { - for _, muscle := range exercise.PrimaryMuscles { - uniqueValues[muscle] = true - } - for _, muscle := range exercise.SecondaryMuscles { - uniqueValues[muscle] = true - } - } - - // Upsert lookup entities in database - if err := db.upsertMuscles(uniqueValues); err != nil { - return nil, err - } - - // Build lookup map - muscleLookupMap, err := db.buildMuscleLookupMap(uniqueValues) - if err != nil { - return nil, err - } - - log.Printf("Upserted lookup entities: %d muscles", len(uniqueValues)) - - return muscleLookupMap, nil -} - -func (db *Database) upsertMuscles(muscles map[string]bool) error { - for name := range muscles { - muscle := Muscle{Name: name} - if err := db.Where("name = ?", name).FirstOrCreate(&muscle).Error; err != nil { - return fmt.Errorf("failed to upsert muscle %s: %w", name, err) - } - } - return nil -} - -// buildLookupMaps populates the lookup maps with IDs from the database -func (db *Database) buildMuscleLookupMap(uniqueValues map[string]bool) (map[string]uint, error) { - // Build muscles map - maps := make(map[string]uint) - for name := range uniqueValues { - var muscle Muscle - if err := db.Where("name = ?", name).First(&muscle).Error; err != nil { - return nil, fmt.Errorf("failed to find muscle %s: %w", name, err) - } - maps[name] = muscle.ID - } - - return maps, nil -} - // upsertExercise creates or updates a single exercise with all its related data -func (db *Database) upsertExercise(importedExercise importedExercise, lookupMap map[string]uint) (didSave, isUpdate bool, err error) { +func (db *Database) upsertExercise(importedExercise importedExercise) (didSave, isUpdate bool, err error) { // First, try to find existing exercise by name var existingExercise Exercise result := db.Where("name = ?", importedExercise.Name).Preload("PrimaryMuscles").Preload("SecondaryMuscles").First(&existingExercise)@@ -104,6 +47,14 @@ }
if importedExercise.Equipment != nil && *importedExercise.Equipment != "" { exercise.Equipment = importedExercise.Equipment } + if len(importedExercise.PrimaryMuscles) > 0 { + primaryMuscles := strings.Join(importedExercise.PrimaryMuscles, ", ") + exercise.PrimaryMuscles = &primaryMuscles + } + if len(importedExercise.SecondaryMuscles) > 0 { + secondaryMuscles := strings.Join(importedExercise.SecondaryMuscles, ", ") + exercise.SecondaryMuscles = &secondaryMuscles + } if len(importedExercise.Instructions) > 0 { // Filter out empty instructions@@ -118,23 +69,17 @@ instructions := strings.Join(filteredInstructions, "\n")
exercise.Instructions = &instructions } - var exerciseID uint var exerciseDataChanged bool - var muscleAssociationsChanged bool if result.Error == nil { // Exercise exists, check if it needs updating isUpdate = true - exerciseID = existingExercise.ID - exercise.ID = exerciseID + exercise.ID = existingExercise.ID exercise.CreatedAt = existingExercise.CreatedAt // Preserve creation time // Check if the exercise data has actually changed exerciseDataChanged = db.exerciseDataChanged(existingExercise, exercise) - // Check if muscle associations have changed - muscleAssociationsChanged = db.muscleAssociationsChanged(existingExercise, importedExercise, lookupMap) - // Only update if something has changed if exerciseDataChanged { if err := db.Save(&exercise).Error; err != nil {@@ -145,21 +90,11 @@ }
} else { // Exercise doesn't exist, create it isUpdate = false - exerciseDataChanged = true // New exercise, so data is "changed" - muscleAssociationsChanged = true // New exercise, so associations are "changed" + exerciseDataChanged = true // New exercise, so data is "changed" if err := db.Create(&exercise).Error; err != nil { return false, false, fmt.Errorf("failed to create exercise: %w", err) } - exerciseID = exercise.ID - didSave = true - } - - // Only update muscle associations if they've changed - if muscleAssociationsChanged { - if err := db.updateMuscleAssociations(exerciseID, importedExercise, lookupMap); err != nil { - return false, false, fmt.Errorf("failed to update muscle associations: %w", err) - } didSave = true }@@ -173,7 +108,9 @@ existing.Category != new.Category ||
!stringPointersEqual(existing.Force, new.Force) || !stringPointersEqual(existing.Mechanic, new.Mechanic) || !stringPointersEqual(existing.Equipment, new.Equipment) || - !stringPointersEqual(existing.Instructions, new.Instructions) + !stringPointersEqual(existing.Instructions, new.Instructions) || + existing.PrimaryMuscles != new.PrimaryMuscles || + existing.SecondaryMuscles != new.SecondaryMuscles } // Helper function to compare string pointers@@ -187,98 +124,6 @@ }
return *a == *b } -// muscleAssociationsChanged compares existing muscle associations with imported data -func (db *Database) muscleAssociationsChanged(existing Exercise, imported importedExercise, lookupMap map[string]uint) bool { - // Convert existing muscle associations to sets for comparison - existingPrimary := make(map[uint]bool) - existingSecondary := make(map[uint]bool) - - for _, muscle := range existing.PrimaryMuscles { - existingPrimary[muscle.ID] = true - } - for _, muscle := range existing.SecondaryMuscles { - existingSecondary[muscle.ID] = true - } - - // Convert imported muscle names to IDs and create sets - importedPrimary := make(map[uint]bool) - importedSecondary := make(map[uint]bool) - - for _, muscleName := range imported.PrimaryMuscles { - if muscleID, ok := lookupMap[muscleName]; ok { - importedPrimary[muscleID] = true - } - } - for _, muscleName := range imported.SecondaryMuscles { - if muscleID, ok := lookupMap[muscleName]; ok { - importedSecondary[muscleID] = true - } - } - - // Compare primary muscles - if len(existingPrimary) != len(importedPrimary) { - return true - } - for muscleID := range existingPrimary { - if !importedPrimary[muscleID] { - return true - } - } - - // Compare secondary muscles - if len(existingSecondary) != len(importedSecondary) { - return true - } - for muscleID := range existingSecondary { - if !importedSecondary[muscleID] { - return true - } - } - - return false -} - -// updateMuscleAssociations replaces muscle associations for an exercise -func (db *Database) updateMuscleAssociations(exerciseID uint, importedExercise importedExercise, lookupMap map[string]uint) error { - exercise := Exercise{ID: exerciseID} - - // Clear existing associations - if err := db.Model(&exercise).Association("PrimaryMuscles").Clear(); err != nil { - return fmt.Errorf("failed to clear primary muscles: %w", err) - } - if err := db.Model(&exercise).Association("SecondaryMuscles").Clear(); err != nil { - return fmt.Errorf("failed to clear secondary muscles: %w", err) - } - - // Add primary muscles - var primaryMuscles []Muscle - for _, muscleName := range importedExercise.PrimaryMuscles { - if muscleID, ok := lookupMap[muscleName]; ok { - primaryMuscles = append(primaryMuscles, Muscle{ID: muscleID}) - } - } - if len(primaryMuscles) > 0 { - if err := db.Model(&exercise).Association("PrimaryMuscles").Append(&primaryMuscles); err != nil { - return fmt.Errorf("failed to add primary muscles: %w", err) - } - } - - // Add secondary muscles - var secondaryMuscles []Muscle - for _, muscleName := range importedExercise.SecondaryMuscles { - if muscleID, ok := lookupMap[muscleName]; ok { - secondaryMuscles = append(secondaryMuscles, Muscle{ID: muscleID}) - } - } - if len(secondaryMuscles) > 0 { - if err := db.Model(&exercise).Association("SecondaryMuscles").Append(&secondaryMuscles); err != nil { - return fmt.Errorf("failed to add secondary muscles: %w", err) - } - } - - return nil -} - // downloadExercises downloads exercises from the JSON URL func downloadExercises() ([]importedExercise, error) { // Download exercises.json from the URL@@ -351,17 +196,11 @@ }
log.Printf("Successfully loaded %d exercises from JSON", len(exercises)) - // Create/update lookup entities and get maps - lookupMaps, err := db.upsertLookupEntities(exercises) - if err != nil { - return errors.New("Failed to upsert lookup entities: " + err.Error()) - } - var successCount, createCount, updateCount int // Import/update exercises for i, exercise := range exercises { - didSave, isUpdate, err := db.upsertExercise(exercise, lookupMaps) + didSave, isUpdate, err := db.upsertExercise(exercise) if err != nil { log.Printf("Failed to upsert exercise %d (%s): %v", i+1, exercise.Name, err) continue
@@ -37,16 +37,8 @@ Mechanic *string `gorm:"size:50" json:"mechanic"`
Equipment *string `gorm:"size:50" json:"equipment"` Instructions *string `json:"instructions"` - PrimaryMuscles []Muscle `gorm:"many2many:exercise_primary_muscles;constraint:OnDelete:CASCADE" json:"primaryMuscles"` - SecondaryMuscles []Muscle `gorm:"many2many:exercise_secondary_muscles;constraint:OnDelete:CASCADE" json:"secondaryMuscles"` - - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -type Muscle struct { - ID uint `gorm:"primaryKey" json:"id"` - Name string `gorm:"uniqueIndex;size:50;not null" json:"name"` + PrimaryMuscles *string `json:"primaryMuscles"` + SecondaryMuscles *string `json:"secondaryMuscles"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"`@@ -211,7 +203,6 @@
// Auto migrate the models in correct order err = conn.AutoMigrate( &User{}, - &Muscle{}, &Exercise{}, &Routine{}, &RoutineItem{},@@ -236,36 +227,3 @@ }
return db, nil } - -// Helper methods for creating and querying routines - -// CreateRoutineWithData creates a routine with all nested data -func (db *Database) CreateRoutineWithData(routine *Routine) error { - return db.Create(routine).Error -} - -// GetRoutineWithItems retrieves a routine with all its nested data -func (db *Database) GetRoutineWithItems(routineID uint) (*Routine, error) { - var routine Routine - err := db.Preload("Items.ExerciseItems.Exercise.PrimaryMuscles"). - Preload("Items.ExerciseItems.Exercise.SecondaryMuscles"). - Preload("Items.ExerciseItems.Sets"). - Order("Items.order_index, Items.ExerciseItems.order_index, Items.ExerciseItems.Sets.order_index"). - First(&routine, routineID).Error - - return &routine, err -} - -// GetRecordRoutineWithData retrieves a completed workout with all nested data -func (db *Database) GetRecordRoutineWithData(recordID uint) (*RecordRoutine, error) { - var record RecordRoutine - err := db.Preload("Routine"). - Preload("RecordItems.RoutineItem"). - Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise.PrimaryMuscles"). - Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise.SecondaryMuscles"). - Preload("RecordItems.RecordExerciseItems.RecordSets.Set"). - Order("RecordItems.order_index, RecordItems.RecordExerciseItems.order_index, RecordItems.RecordExerciseItems.RecordSets.order_index"). - First(&record, recordID).Error - - return &record, err -}
@@ -0,0 +1,10 @@
+package ui + +import "strings" + +func capitalize(s string) string { + if s == "" { + return s + } + return strings.ToUpper(s[:1]) + s[1:] +}
@@ -0,0 +1,85 @@
+package ui + +import ( + "bytes" + "html/template" + "net/http" + "os" +) + +const ( + base = "base" + templates = "templates" + ps = string(os.PathSeparator) + + basePath = "templates" + ps + base + ".gohtml" + homePath = "templates" + ps + "home.gohtml" + exercisesPath = "templates" + ps + "exercises.gohtml" + routinesPath = "templates" + ps + "routines.gohtml" + workoutsPath = "templates" + ps + "workouts.gohtml" + profilePath = "templates" + ps + "profile.gohtml" +) + +var ( + tmpl map[string]*template.Template + funcMap = template.FuncMap{ + "capitalize": capitalize, + } +) + +type PageData struct { + Page string +} + +func parseTemplate(path string) *template.Template { + return template.Must(template.New(base).Funcs(funcMap).ParseFiles(path, basePath)) +} + +func executeTemplateSafe(w http.ResponseWriter, t string, data any) { + var buf bytes.Buffer + if err := tmpl[t].ExecuteTemplate(&buf, base, data); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + buf.WriteTo(w) +} + +func getHome(w http.ResponseWriter, r *http.Request) { + executeTemplateSafe(w, homePath, &PageData{Page: "home"}) +} + +func getExercises(w http.ResponseWriter, r *http.Request) { + executeTemplateSafe(w, exercisesPath, &PageData{Page: "exercises"}) +} + +func getRoutines(w http.ResponseWriter, r *http.Request) { + executeTemplateSafe(w, routinesPath, &PageData{Page: "routines"}) +} + +func getWorkouts(w http.ResponseWriter, r *http.Request) { + executeTemplateSafe(w, workoutsPath, &PageData{Page: "workouts"}) +} + +func getProfile(w http.ResponseWriter, r *http.Request) { + executeTemplateSafe(w, profilePath, &PageData{Page: "profile"}) +} + +func InitServeMux(s *http.ServeMux) { + tmpl = make(map[string]*template.Template) + + tmpl[homePath] = parseTemplate(homePath) + tmpl[exercisesPath] = parseTemplate(exercisesPath) + tmpl[routinesPath] = parseTemplate(routinesPath) + tmpl[workoutsPath] = parseTemplate(workoutsPath) + tmpl[profilePath] = parseTemplate(profilePath) + + s.HandleFunc("GET /", getHome) + s.HandleFunc("GET /exercises", getExercises) + s.HandleFunc("GET /routines", getRoutines) + s.HandleFunc("GET /workouts", getWorkouts) + s.HandleFunc("GET /profile", getProfile) + + s.HandleFunc("GET /static/", func(w http.ResponseWriter, r *http.Request) { + http.StripPrefix("/static/", http.FileServer(http.Dir("static"))).ServeHTTP(w, r) + }) +}
@@ -0,0 +1,117 @@
+:root { + --bg-color: #121212; + --text-color: #ffffff; + --sidebar-bg: #1e1e1e; + --nav-active: #0a84ff; +} + +[data-theme="light"] { + --bg-color: #ffffff; + --text-color: #333333; + --sidebar-bg: #f0f0f0; + --nav-active: #007aff; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, + Ubuntu, Cantarell, sans-serif; + margin: 0; + padding: 0; + background-color: var(--bg-color); + color: var(--text-color); + transition: background-color 0.3s, color 0.3s; +} + +html:not([data-theme]) body { + display: none; +} + + +.loading { + transition: none; +} + +.container { + display: flex; + min-height: 100vh; +} + +.content { + flex: 1; + padding: 20px; + padding-bottom: 80px; /* Space for mobile nav */ +} + +/* Navigation styles */ +nav { + background-color: var(--sidebar-bg); + transition: background-color 0.3s; +} + +.nav-link { + display: flex; + align-items: center; + padding: 12px 16px; + text-decoration: none; + color: var(--text-color); +} + +.nav-link.active { + color: var(--nav-active); +} + +.nav-icon { + margin-right: 8px; + font-size: 1.2em; +} + +/* Theme toggle */ +.theme-toggle { + position: fixed; + top: 20px; + right: 20px; + background: none; + border: none; + cursor: pointer; + font-size: 1.5em; + color: var(--text-color); +} + +/* Mobile navigation (bottom) */ +@media (max-width: 768px) { + .sidebar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 60px; + display: flex; + justify-content: space-around; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); + z-index: 1000; + } + + .nav-link { + flex-direction: column; + font-size: 0.8em; + padding: 8px 0; + } + + .nav-icon { + margin-right: 0; + margin-bottom: 4px; + } +} + +/* Desktop navigation (sidebar) */ +@media (min-width: 769px) { + .sidebar { + width: 220px; + min-height: 100vh; + box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); + } + + .nav-link { + padding: 12px 24px; + } +}
@@ -0,0 +1,51 @@
+.table-controls { + margin-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +#search-input { + padding: 8px; + width: 250px; +} + +.column-toggles { + display: flex; + gap: 10px; + align-items: center; +} + +.data-table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +.data-table th, +.data-table td { + padding: 10px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.data-table th.sortable { + cursor: pointer; + position: relative; +} + +.data-table th.sortable:after { + content: "⇵"; + margin-left: 5px; + opacity: 0.5; +} + +.data-table th.asc:after { + content: "↑"; + opacity: 1; +} + +.data-table th.desc:after { + content: "↓"; + opacity: 1; +}
@@ -0,0 +1,93 @@
+// Set up column visibility toggles + +let sortColumn = "name"; +let sortDirection = "asc"; +let data = []; +let columns = []; + +function setupColumnToggles(columns) { + const togglesContainer = document.querySelector(".column-toggles"); + columns.forEach((column) => { + const label = document.createElement("label"); + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = column.visible; + checkbox.dataset.column = column.id; + checkbox.addEventListener("change", function () { + columns.find((col) => col.id === column.id).visible = this.checked; + renderTable(); + }); + + label.appendChild(checkbox); + label.appendChild(document.createTextNode(` ${column.label}`)); + togglesContainer.appendChild(label); + }); +} + +// Sort function +function sortData(columnId) { + if (sortColumn === columnId) { + sortDirection = sortDirection === "asc" ? "desc" : "asc"; + } else { + sortColumn = columnId; + sortDirection = "asc"; + } + renderTable(); +} + +function renderTable() { + const tableHead = document.querySelector("#data-table thead tr"); + const tableBody = document.querySelector("#data-table tbody"); + const searchTerm = document + .getElementById("search-input") + .value.toLowerCase(); + + // Clear existing content + tableHead.innerHTML = ""; + tableBody.innerHTML = ""; + + // Add table headers + columns.forEach((column) => { + if (column.visible) { + const th = document.createElement("th"); + th.textContent = column.label; + th.classList.add("sortable"); + if (sortColumn === column.id) { + th.classList.add(sortDirection); + } + th.addEventListener("click", () => sortData(column.id)); + tableHead.appendChild(th); + } + }); + + // Filter and sort data + const filteredData = data.filter((d) => { + return Object.values(d).some((value) => + String(value).toLowerCase().includes(searchTerm) + ); + }); + + const sortedData = [...filteredData].sort((a, b) => { + const valueA = String(a[sortColumn] || ""); + const valueB = String(b[sortColumn] || ""); + + return sortDirection === "asc" + ? valueA.localeCompare(valueB) + : valueB.localeCompare(valueA); + }); + + // Populate table rows + sortedData.forEach((r) => { + const row = document.createElement("tr"); + + columns.forEach((column) => { + if (column.visible) { + const cell = document.createElement("td"); + cell.textContent = r[column.id] || ""; + row.appendChild(cell); + } + }); + + tableBody.appendChild(row); + }); +}
@@ -0,0 +1,50 @@
+// Check for user preference +document.addEventListener("DOMContentLoaded", () => { + // Check for saved theme preference or use system preference + const savedTheme = localStorage.getItem("theme"); + if (savedTheme) { + document.documentElement.setAttribute("data-theme", savedTheme); + updateThemeToggle(savedTheme); + } else if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + document.documentElement.setAttribute("data-theme", "dark"); + updateThemeToggle("dark"); + } + + // Listen for system preference changes + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", (e) => { + const newTheme = e.matches ? "dark" : "light"; + if (!localStorage.getItem("theme")) { + document.documentElement.setAttribute("data-theme", newTheme); + updateThemeToggle(newTheme); + } + }); + + // Theme toggle button + const themeToggle = document.getElementById("themeToggle"); + themeToggle.addEventListener("click", () => { + const currentTheme = + document.documentElement.getAttribute("data-theme") || "light"; + const newTheme = currentTheme === "light" ? "dark" : "light"; + + document.documentElement.setAttribute("data-theme", newTheme); + localStorage.setItem("theme", newTheme); + updateThemeToggle(newTheme); + }); + + setTimeout(() => { + for (item of document.getElementsByClassName("loading")) { + console.log(item); + item.classList.remove("loading"); + } + }, 500); +}); + +function updateThemeToggle(theme) { + const themeToggle = document.getElementById("themeToggle"); + themeToggle.textContent = theme === "dark" ? "☀️" : "🌑"; +}
@@ -0,0 +1,43 @@
+{{ define "base" }}<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Go Lift - {{ capitalize .Page }}</title> + <link rel="stylesheet" href="/static/styles.css"> +</head> +<body class="loading"> + <div class="container"> + <nav class="sidebar"> + <a href="/" class="nav-link {{ if eq .Page "home" }}active{{ end }}"> + <span class="nav-icon">🏠</span> + <span>Home</span> + </a> + <a href="/exercises" class="nav-link {{ if eq .Page "exercises" }}active{{ end }}"> + <span class="nav-icon">🏋️♂️</span> + <span>Exercises</span> + </a> + <a href="/routines" class="nav-link {{ if eq .Page "routines" }}active{{ end }}"> + <span class="nav-icon">📝</span> + <span>Routines</span> + </a> + <a href="/workouts" class="nav-link {{ if eq .Page "workouts" }}active{{ end }}"> + <span class="nav-icon">💪</span> + <span>Workouts</span> + </a> + <a href="/profile" class="nav-link {{ if eq .Page "profile" }}active{{ end }}"> + <span class="nav-icon">👤</span> + <span>Profile</span> + </a> + </nav> + + <main class="content"> + {{ template "body" . }} + </main> + </div> + + <button class="theme-toggle" id="themeToggle">🌓</button> + + <script src="/static/theme.js"></script> +</body> +</html>{{ end }}
@@ -0,0 +1,60 @@
+{{ define "body" }} +<link rel="stylesheet" href="/static/table.css"> +<h1>Exercises</h1> + +<!-- Table controls --> +<div class="table-controls"> + <input type="text" id="search-input" placeholder="Search exercises..."> + <div class="column-toggles"> + <span>Toggle columns:</span> + <!-- Column toggles will be added here --> + </div> +</div> + +<!-- Exercises table --> +<table id="data-table" class="data-table"> + <thead> + <tr> + <!-- Table headers will be added here --> + </tr> + </thead> + <tbody> + <!-- Exercise rows will be populated here --> + </tbody> +</table> + +<script src="/static/table.js"></script> +<script> +document.addEventListener("DOMContentLoaded", function () { + // Fetch and display exercises + fetch("/api/exercises") + .then((response) => response.json()) + .then((exercises) => { + columns = [ + { id: "id", label: "ID", visible: false }, + { id: "name", label: "Name", visible: true }, + { id: "equipment", label: "Equipment", visible: true }, + { id: "primaryMuscles", label: "Primary muscles", visible: true }, + { id: "secondaryMuscles", label: "Secondary muscles", visible: true }, + { id: "category", label: "Category", visible: false }, + { id: "force", label: "Force", visible: false }, + { id: "instructions", label: "Instructions", visible: false }, + { id: "level", label: "Level", visible: false }, + { id: "mechanic", label: "Mechanic", visible: false }, + ]; + data = exercises; + console.log(data); + setupColumnToggles(columns); + renderTable(); + }) + .catch((error) => console.error("Error fetching exercises:", error)); + + // Search functionality + document + .getElementById("search-input") + .addEventListener("input", function () { + renderTable(); + }); +}); +</script> +{{ end }}
@@ -0,0 +1,4 @@
+{{ define "body" }} +<h1>Go Lift</h1> +<p>Welcome to your fitness journey!</p> +{{ end }}
@@ -0,0 +1,241 @@
+{{ define "body" }} +<h1>Go Lift</h1> +<div class="profile-container"> + <h2>User Profile</h2> + + <div class="button-group"> + <button id="editBtn" onclick="toggleEditMode(true)">Edit</button> + <button id="saveBtn" onclick="saveProfile()" style="display:none">Save</button> + <button id="cancelBtn" onclick="toggleEditMode(false)" style="display:none">Cancel</button> + </div> + + <form id="profileForm"> + <div class="form-group"> + <label for="name">Name:</label> + <input type="text" id="name" name="name" value="" disabled> + </div> + + <div class="form-group"> + <label>Gender:</label> + <div class="radio-group"> + <input type="radio" id="male" name="isFemale" value="false" disabled> + <label for="male">Male</label> + <input type="radio" id="female" name="isFemale" value="true" disabled> + <label for="female">Female</label> + </div> + </div> + + <div class="form-group"> + <label for="height">Height (cm):</label> + <input type="number" id="height" name="height" value="" step="0.1" disabled> + </div> + + <div class="form-group"> + <label for="weight">Weight (kg):</label> + <input type="number" id="weight" name="weight" value="" step="0.1" disabled> + </div> + + <div class="form-group"> + <label for="birthDate">Birth Date:</label> + <input type="date" id="birthDate" name="birthDate" value="" disabled> + </div> + </form> +</div> + +<script> + // Store original values for cancel functionality + let originalValues = {}; + + // Fetch user profile data from API + function fetchProfileData() { + fetch('/api/users/1') + .then(response => { + if (!response.ok) { + throw new Error('Failed to fetch profile data'); + } + return response.json(); + }) + .then(data => { + populateFormData(data); + saveOriginalValues(); + }) + .catch(error => { + console.error('Error:', error); + alert('Failed to load profile: ' + error.message); + }); + } + + // Populate form with profile data + function populateFormData(userData) { + document.getElementById('name').value = userData.name || ''; + document.getElementById('height').value = userData.height || ''; + document.getElementById('weight').value = userData.weight || ''; + + if (userData.birthDate) { + document.getElementById('birthDate').value = userData.birthDate.split('T')[0]; + } + + // Set gender radio button + if (userData.isFemale !== undefined) { + document.getElementById('female').checked = userData.isFemale; + document.getElementById('male').checked = !userData.isFemale; + } + } + + // Capture initial values + function saveOriginalValues() { + const form = document.getElementById('profileForm'); + const formElements = form.elements; + + for (let i = 0; i < formElements.length; i++) { + const element = formElements[i]; + if (element.name) { + if (element.type === 'radio') { + if (element.checked) { + originalValues[element.name] = element.value; + } + } else { + originalValues[element.name] = element.value; + } + } + } + } + + // Toggle between view and edit modes + function toggleEditMode(isEdit) { + const form = document.getElementById('profileForm'); + const formElements = form.elements; + + // If entering edit mode, save original values + if (isEdit) { + saveOriginalValues(); + } + + // Toggle form field disabled state + for (let i = 0; i < formElements.length; i++) { + formElements[i].disabled = !isEdit; + } + + // Toggle button visibility + document.getElementById('editBtn').style.display = isEdit ? 'none' : 'inline-block'; + document.getElementById('saveBtn').style.display = isEdit ? 'inline-block' : 'none'; + document.getElementById('cancelBtn').style.display = isEdit ? 'inline-block' : 'none'; + + // If canceling, restore original values + if (!isEdit) { + restoreOriginalValues(); + } + } + + // Restore original form values + function restoreOriginalValues() { + const form = document.getElementById('profileForm'); + + for (const name in originalValues) { + const elements = form.elements[name]; + + if (elements.length > 1) { + // Handle radio buttons + for (let i = 0; i < elements.length; i++) { + elements[i].checked = elements[i].value === originalValues[name]; + } + } else { + elements.value = originalValues[name]; + } + } + } + + // Save profile changes + function saveProfile() { + const form = document.getElementById('profileForm'); + const formData = new FormData(form); + const userData = {}; + + // Convert form data to JSON + for (const [key, value] of formData.entries()) { + if (key === 'isFemale') { + userData[key] = value === 'true'; + } else if (key === 'height' || key === 'weight') { + userData[key] = value ? parseFloat(value) : null; + } else if (key === 'birthDate') { + userData[key] = value ? new Date(value).toISOString() : null; + } else { + userData[key] = value || null; + } + } + + // Send PUT request + fetch('/api/users/1', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userData) + }) + .then(response => { + if (!response.ok) { + throw new Error('Failed to update profile'); + } + return response.json(); + }) + .then(data => { + toggleEditMode(false); + // Refresh data + fetchProfileData(); + }) + .catch(error => { + console.error('Error:', error); + alert('Failed to update profile: ' + error.message); + }); + } + + // Initialize when page loads + document.addEventListener('DOMContentLoaded', function() { + fetchProfileData(); + }); +</script> + +<style> + .profile-container { + max-width: 600px; + margin: 0 auto; + padding: 20px; + } + + .form-group { + margin-bottom: 15px; + } + + .form-group label { + display: block; + margin-bottom: 5px; + font-weight: bold; + } + + .form-group input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + } + + .radio-group { + display: flex; + gap: 15px; + } + + .radio-group input { + width: auto; + } + + .button-group { + margin-bottom: 20px; + } + + button { + padding: 8px 16px; + margin-right: 10px; + cursor: pointer; + } +</style> +{{ end }}
@@ -0,0 +1,4 @@
+{{ define "body" }} +<h1>Go Lift</h1> +<p>Routines!</p> +{{ end }}
@@ -0,0 +1,4 @@
+{{ define "body" }} +<h1>Go Lift</h1> +<p>Workouts!</p> +{{ end }}
@@ -1,24 +0,0 @@
-# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw?
@@ -1,31 +0,0 @@
-# Fitness Tracker App (Vite + React + TypeScript) - -This project is a mobile-first, responsive fitness tracker app built with Vite, React, and TypeScript. - -## Features -- **Home**: View all completed workouts and key statistics. -- **Workouts**: Browse all saved routines and start a new workout from a routine. -- **New Routine**: Create a new routine by combining different exercises. -- **New Exercise**: Add a new exercise to the database. -- **New Workout**: Log a new workout (an execution of a routine). -- **Profile**: View user profile (name, sex, weight, date of birth). - -### UI/UX -- Single-user, no registration or login. -- Entirely in English. -- Mobile-first design, fully responsive. -- On mobile: fixed bottom navigation bar (Apple style) with "Home", "Workout", "Profile". -- On desktop: navigation menu is positioned appropriately for desktop use. - -## Getting Started - -```bash -npm install -npm run dev -``` - -Open the app in your browser at the provided local address. - ---- - -This project was bootstrapped with Vite using the React + TypeScript template.
@@ -1,28 +0,0 @@
-import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' - -export default tseslint.config( - { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -)
@@ -1,13 +0,0 @@
-<!doctype html> -<html lang="en"> - <head> - <meta charset="UTF-8" /> - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Vite + React + TS</title> - </head> - <body> - <div id="root"></div> - <script type="module" src="/src/main.tsx"></script> - </body> -</html>
@@ -1,3382 +0,0 @@
-{ - "name": "ui", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ui", - "version": "0.0.0", - "dependencies": { - "@types/react-router-dom": "^5.3.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-icons": "^5.5.0", - "react-router-dom": "^7.6.0" - }, - "devDependencies": { - "@eslint/js": "^9.25.0", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.2", - "@vitejs/plugin-react": "^4.4.1", - "eslint": "^9.25.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.19", - "globals": "^16.0.0", - "typescript": "~5.8.3", - "typescript-eslint": "^8.30.1", - "vite": "^6.3.5" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", - "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helpers": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", - "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", - "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", - "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", - "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", - "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", - "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", - "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", - "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", - "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", - "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", - "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", - "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", - "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", - "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", - "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", - "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", - "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", - "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", - "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", - "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", - "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", - "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", - "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", - "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", - "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", - "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.14.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", - "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", - "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", - "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", - "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", - "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", - "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", - "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", - "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", - "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", - "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", - "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", - "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", - "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", - "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", - "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", - "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", - "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", - "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", - "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", - "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", - "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.0.0" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", - "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/type-utils": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", - "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", - "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", - "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.1", - "@typescript-eslint/utils": "8.32.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", - "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", - "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/visitor-keys": "8.32.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", - "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.1", - "@typescript-eslint/types": "8.32.1", - "@typescript-eslint/typescript-estree": "8.32.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", - "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.1", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", - "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.26.10", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.25.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", - "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.4", - "@esbuild/android-arm": "0.25.4", - "@esbuild/android-arm64": "0.25.4", - "@esbuild/android-x64": "0.25.4", - "@esbuild/darwin-arm64": "0.25.4", - "@esbuild/darwin-x64": "0.25.4", - "@esbuild/freebsd-arm64": "0.25.4", - "@esbuild/freebsd-x64": "0.25.4", - "@esbuild/linux-arm": "0.25.4", - "@esbuild/linux-arm64": "0.25.4", - "@esbuild/linux-ia32": "0.25.4", - "@esbuild/linux-loong64": "0.25.4", - "@esbuild/linux-mips64el": "0.25.4", - "@esbuild/linux-ppc64": "0.25.4", - "@esbuild/linux-riscv64": "0.25.4", - "@esbuild/linux-s390x": "0.25.4", - "@esbuild/linux-x64": "0.25.4", - "@esbuild/netbsd-arm64": "0.25.4", - "@esbuild/netbsd-x64": "0.25.4", - "@esbuild/openbsd-arm64": "0.25.4", - "@esbuild/openbsd-x64": "0.25.4", - "@esbuild/sunos-x64": "0.25.4", - "@esbuild/win32-arm64": "0.25.4", - "@esbuild/win32-ia32": "0.25.4", - "@esbuild/win32-x64": "0.25.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", - "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.27.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", - "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz", - "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.0" - } - }, - "node_modules/react-icons": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", - "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", - "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz", - "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==", - "license": "MIT", - "dependencies": { - "react-router": "7.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", - "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.0", - "@rollup/rollup-android-arm64": "4.41.0", - "@rollup/rollup-darwin-arm64": "4.41.0", - "@rollup/rollup-darwin-x64": "4.41.0", - "@rollup/rollup-freebsd-arm64": "4.41.0", - "@rollup/rollup-freebsd-x64": "4.41.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", - "@rollup/rollup-linux-arm-musleabihf": "4.41.0", - "@rollup/rollup-linux-arm64-gnu": "4.41.0", - "@rollup/rollup-linux-arm64-musl": "4.41.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", - "@rollup/rollup-linux-riscv64-gnu": "4.41.0", - "@rollup/rollup-linux-riscv64-musl": "4.41.0", - "@rollup/rollup-linux-s390x-gnu": "4.41.0", - "@rollup/rollup-linux-x64-gnu": "4.41.0", - "@rollup/rollup-linux-x64-musl": "4.41.0", - "@rollup/rollup-win32-arm64-msvc": "4.41.0", - "@rollup/rollup-win32-ia32-msvc": "4.41.0", - "@rollup/rollup-win32-x64-msvc": "4.41.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", - "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.32.1", - "@typescript-eslint/parser": "8.32.1", - "@typescript-eslint/utils": "8.32.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -}
@@ -1,32 +0,0 @@
-{ - "name": "ui", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@types/react-router-dom": "^5.3.3", - "react": "^19.1.0", - "react-dom": "^19.1.0", - "react-icons": "^5.5.0", - "react-router-dom": "^7.6.0" - }, - "devDependencies": { - "@eslint/js": "^9.25.0", - "@types/react": "^19.1.2", - "@types/react-dom": "^19.1.2", - "@vitejs/plugin-react": "^4.4.1", - "eslint": "^9.25.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.19", - "globals": "^16.0.0", - "typescript": "~5.8.3", - "typescript-eslint": "^8.30.1", - "vite": "^6.3.5" - } -}
@@ -1,216 +0,0 @@
-:root { - --primary-color: #007aff; - --secondary-color: #5ac8fa; - --background-color: #f5f5f7; - --text-color: #1d1d1f; - --light-gray: #d1d1d6; - --dark-gray: #8e8e93; - --success-color: #34c759; - --danger-color: #ff3b30; - --warning-color: #ff9500; - --border-radius: 8px; - --spacing-xs: 4px; - --spacing-sm: 8px; - --spacing-md: 16px; - --spacing-lg: 24px; - --spacing-xl: 32px; - --bottom-nav-height: 60px; - --side-nav-width: 250px; - --header-height: 60px; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; - background-color: var(--background-color); - color: var(--text-color); - line-height: 1.5; -} - -#root { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -/* App container */ -.app-container { - flex: 1; - display: flex; - flex-direction: column; - max-width: 100%; -} - -/* Mobile layout */ -.mobile-layout .content-area { - padding: var(--spacing-md); - padding-bottom: calc(var(--bottom-nav-height) + var(--spacing-md)); -} - -/* Desktop layout */ -.desktop-layout { - flex-direction: row; -} - -.desktop-layout .content-area { - flex: 1; - margin-left: var(--side-nav-width); - padding: var(--spacing-lg); -} - -/* Page styling */ -.page { - max-width: 1200px; - margin: 0 auto; -} - -/* Typography */ -h1 { - font-size: 24px; - font-weight: 600; - margin-bottom: var(--spacing-lg); -} - -h2 { - font-size: 20px; - font-weight: 500; - margin-bottom: var(--spacing-md); -} - -h3 { - font-size: 18px; - font-weight: 500; - margin-bottom: var(--spacing-md); -} - -/* Form elements */ -.form-group { - margin-bottom: var(--spacing-lg); -} - -label { - display: block; - margin-bottom: var(--spacing-xs); - font-weight: 500; -} - -input, select, textarea { - width: 100%; - padding: var(--spacing-sm); - border: 1px solid var(--light-gray); - border-radius: var(--border-radius); - font-size: 16px; - background-color: white; -} - -input:focus, select:focus, textarea:focus { - outline: none; - border-color: var(--primary-color); -} - -/* Buttons */ -.btn { - display: inline-block; - padding: 10px 16px; - border: none; - border-radius: var(--border-radius); - font-size: 16px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s; - text-align: center; -} - -.btn-primary { - background-color: var(--primary-color); - color: white; -} - -.btn-secondary { - background-color: var(--light-gray); - color: var(--text-color); -} - -.btn-success { - background-color: var(--success-color); - color: white; -} - -.btn-danger { - background-color: var(--danger-color); - color: white; -} - -.btn-block { - display: block; - width: 100%; -} - -/* Cards */ -.card { - background-color: white; - border-radius: var(--border-radius); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - padding: var(--spacing-lg); - margin-bottom: var(--spacing-lg); -} - -/* Lists */ -.list-item { - padding: var(--spacing-md); - border-bottom: 1px solid var(--light-gray); -} - -.list-item:last-child { - border-bottom: none; -} - -/* Utilities */ -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.flex { - display: flex; -} - -.flex-col { - flex-direction: column; -} - -.justify-between { - justify-content: space-between; -} - -.items-center { - align-items: center; -} - -.mt-sm { margin-top: var(--spacing-sm); } -.mt-md { margin-top: var(--spacing-md); } -.mt-lg { margin-top: var(--spacing-lg); } - -.mb-sm { margin-bottom: var(--spacing-sm); } -.mb-md { margin-bottom: var(--spacing-md); } -.mb-lg { margin-bottom: var(--spacing-lg); } - -/* Responsive layouts */ -@media (max-width: 767px) { - h1 { - font-size: 22px; - } - - h2 { - font-size: 18px; - } -}
@@ -1,50 +0,0 @@
-import { useEffect, useState } from "react"; -import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom"; -import HomePage from "./pages/HomePage"; -import WorkoutsPage from "./pages/WorkoutsPage"; -import NewRoutinePage from "./pages/NewRoutinePage"; -import NewExercisePage from "./pages/NewExercisePage"; -import NewWorkoutPage from "./pages/NewWorkoutPage"; -import ProfilePage from "./pages/ProfilePage"; -import BottomNav from "./components/BottomNav"; -import { AppProvider } from "./context/AppContext"; -import "./App.css"; - -function App() { - const [isMobile, setIsMobile] = useState(window.innerWidth < 768); - - // Check for device width to determine if mobile or desktop - useEffect(() => { - const handleResize = () => { - setIsMobile(window.innerWidth < 768); - }; - - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); - }; - }, []); - - return ( - <AppProvider> - <Router> - <div className={`app-container ${isMobile ? 'mobile-layout' : 'desktop-layout'}`}> - <div className="content-area"> - <Routes> - <Route path="/" element={<Navigate to="/home" replace />} /> - <Route path="/home" element={<HomePage />} /> - <Route path="/workouts" element={<WorkoutsPage />} /> - <Route path="/new-routine" element={<NewRoutinePage />} /> - <Route path="/new-exercise" element={<NewExercisePage />} /> - <Route path="/new-workout" element={<NewWorkoutPage />} /> - <Route path="/profile" element={<ProfilePage />} /> - </Routes> - </div> - <BottomNav isMobile={isMobile} /> - </div> - </Router> - </AppProvider> - ); -} - -export default App;
@@ -1,117 +0,0 @@
-.bottom-nav.mobile { - position: fixed; - left: 0; - right: 0; - bottom: 0; - height: var(--bottom-nav-height); - background: #fff; - border-top: 1px solid var(--light-gray); - display: flex; - justify-content: space-around; - align-items: center; - z-index: 1000; - box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); -} - -.bottom-nav.mobile a { - color: var(--dark-gray); - text-decoration: none; - font-size: 0.75rem; - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 8px 0; - transition: color 0.2s; -} - -.bottom-nav.mobile a span { - margin-top: 4px; -} - -.bottom-nav.mobile a.active { - color: var(--primary-color); - font-weight: 600; -} - -/* Desktop side navigation */ -.side-nav.desktop { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: var(--side-nav-width); - background-color: white; - border-right: 1px solid var(--light-gray); - padding: var(--spacing-lg) var(--spacing-md); - display: flex; - flex-direction: column; - z-index: 1000; -} - -.side-nav .app-title { - font-size: 24px; - font-weight: 700; - color: var(--primary-color); - margin-bottom: var(--spacing-xl); - padding: var(--spacing-md); -} - -.side-nav .nav-links { - display: flex; - flex-direction: column; - gap: 8px; -} - -.side-nav a { - display: flex; - align-items: center; - padding: 10px 16px; - border-radius: var(--border-radius); - color: var(--text-color); - text-decoration: none; - transition: all 0.2s; -} - -.side-nav a span { - margin-left: 12px; -} - -.side-nav a:hover { - background-color: rgba(0, 122, 255, 0.1); -} - -.side-nav a.active { - background-color: var(--primary-color); - color: white; -} - -.side-nav .sub-menu { - margin: var(--spacing-md) 0; - padding-left: var(--spacing-md); - border-left: 2px solid var(--light-gray); -} - -.side-nav .sub-menu-title { - font-size: 14px; - font-weight: 600; - color: var(--dark-gray); - margin-bottom: var(--spacing-sm); - padding-left: var(--spacing-md); -} -@media (min-width: 768px) { - .bottom-nav { - position: static; - height: 48px; - border-top: none; - border-bottom: 1px solid #eee; - justify-content: flex-end; - background: #fafbfc; - } - .bottom-nav a { - flex: none; - margin-left: 2em; - font-size: 1rem; - } -}
@@ -1,70 +0,0 @@
-import { NavLink } from "react-router-dom"; -import { - FaHome, - FaDumbbell, - FaUserAlt, - FaPlus -} from "react-icons/fa"; -import "./BottomNav.css"; - -interface BottomNavProps { - isMobile: boolean; -} - -const BottomNav = ({ isMobile }: BottomNavProps) => { - if (isMobile) { - return ( - <nav className="bottom-nav mobile"> - <NavLink to="/home" className={({ isActive }) => isActive ? "active" : ""}> - <FaHome size={20} /> - <span>Home</span> - </NavLink> - <NavLink to="/workouts" className={({ isActive }) => isActive ? "active" : ""}> - <FaDumbbell size={20} /> - <span>Workouts</span> - </NavLink> - <NavLink to="/profile" className={({ isActive }) => isActive ? "active" : ""}> - <FaUserAlt size={20} /> - <span>Profile</span> - </NavLink> - </nav> - ); - } - - return ( - <nav className="side-nav desktop"> - <div className="app-title">Go Lift</div> - <div className="nav-links"> - <NavLink to="/home" className={({ isActive }) => isActive ? "active" : ""}> - <FaHome size={20} /> - <span>Home</span> - </NavLink> - <NavLink to="/workouts" className={({ isActive }) => isActive ? "active" : ""}> - <FaDumbbell size={20} /> - <span>Workouts</span> - </NavLink> - <div className="sub-menu"> - <div className="sub-menu-title">Create New</div> - <NavLink to="/new-exercise" className={({ isActive }) => isActive ? "active" : ""}> - <FaPlus size={16} /> - <span>New Exercise</span> - </NavLink> - <NavLink to="/new-routine" className={({ isActive }) => isActive ? "active" : ""}> - <FaPlus size={16} /> - <span>New Routine</span> - </NavLink> - <NavLink to="/new-workout" className={({ isActive }) => isActive ? "active" : ""}> - <FaPlus size={16} /> - <span>New Workout</span> - </NavLink> - </div> - <NavLink to="/profile" className={({ isActive }) => isActive ? "active" : ""}> - <FaUserAlt size={20} /> - <span>Profile</span> - </NavLink> - </div> - </nav> - ); -}; - -export default BottomNav;
@@ -1,78 +0,0 @@
-import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'; -import type { User } from '../types/models'; -import { userService } from '../services/api'; - -interface AppContextType { - user: User | null; - isLoading: boolean; - error: string | null; - updateUser: (profile: User) => Promise<void>; -} - -const defaultProfile: User = { - name: 'User', - isFemale: false, - weight: 85, - height: 180, - birthDate: "01/01/1990", -}; - -const AppContext = createContext<AppContextType>({ - user: defaultProfile, - isLoading: false, - error: null, - updateUser: async () => {}, -}); - -export const useAppContext = () => useContext(AppContext); - -interface AppProviderProps { - children: ReactNode; -} - -export const AppProvider = ({ children }: AppProviderProps) => { - const [user, setUser] = useState<User | null>(null); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - - useEffect(() => { - const loadProfile = async () => { - try { - setIsLoading(true); - const profile = await userService.get(1); - setUser(profile); - setError(null); - } catch (err) { - console.error('Failed to load user profile:', err); - // For the single-user mode, create a default profile if none exists - setUser(defaultProfile); - setError('Could not load profile. Using default settings.'); - } finally { - setIsLoading(false); - } - }; - - loadProfile(); - }, []); - - const updateUser = async (profile: User) => { - try { - setIsLoading(true); - const updatedProfile = await userService.update(1, profile); - setUser(updatedProfile); - setError(null); - } catch (err) { - console.error('Failed to update profile:', err); - setError('Failed to update profile. Please try again.'); - throw err; - } finally { - setIsLoading(false); - } - }; - - return ( - <AppContext.Provider value={{ user, isLoading, error, updateUser }}> - {children} - </AppContext.Provider> - ); -};
@@ -1,66 +0,0 @@
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); - -:root { - line-height: 1.5; - font-weight: 400; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -html { - height: 100%; -} - -body { - margin: 0; - min-height: 100vh; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, - Arial, sans-serif; - background-color: var(--background-color, #f5f5f7); - color: var(--text-color, #1d1d1f); -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -}
@@ -1,10 +0,0 @@
-import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' - -createRoot(document.getElementById('root')!).render( - <StrictMode> - <App /> - </StrictMode>, -)
@@ -1,245 +0,0 @@
-import { useEffect, useState } from 'react'; -import { FaCalendarCheck, FaClock, FaDumbbell } from 'react-icons/fa'; -import type { RecordRoutine, WorkoutStats } from '../types/models'; -import { WorkoutService } from '../services/api'; - -const HomePage = () => { - const [stats, setStats] = useState<WorkoutStats | null>(null); - const [loading, setLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - - useEffect(() => { - const fetchStats = async () => { - try { - setLoading(true); - const data = await WorkoutService.getStats(); - setStats(data); - setError(null); - } catch (err) { - console.error('Failed to fetch workout stats:', err); - setError('Could not load workout statistics. Please try again later.'); - } finally { - setLoading(false); - } - }; - - fetchStats(); - }, []); - - const formatDate = (dateStr: string) => { - const date = new Date(dateStr); - return new Intl.DateTimeFormat('en-US', { - weekday: 'short', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }).format(date); - }; - - const calculateDuration = (start: string, end: string) => { - const startTime = new Date(start).getTime(); - const endTime = new Date(end).getTime(); - const durationMinutes = Math.round((endTime - startTime) / (1000 * 60)); - - return `${durationMinutes} min`; - }; - - if (loading) { - return ( - <div className="page home-page"> - <h1>Home</h1> - <div className="loading">Loading workout data...</div> - </div> - ); - } - - if (error) { - return ( - <div className="page home-page"> - <h1>Home</h1> - <div className="error-message">{error}</div> - </div> - ); - } - - // Display placeholder if no stats - if (!stats) { - return ( - <div className="page home-page"> - <h1>Workout Overview</h1> - <div className="card"> - <h2>Welcome to Go Lift!</h2> - <p>Start by adding exercises and creating your first workout routine.</p> - <div className="mt-lg"> - <a href="/workouts" className="btn btn-primary">Go to Workouts</a> - </div> - </div> - </div> - ); - } - - return ( - <div className="page home-page"> - <h1>Workout Overview</h1> - - {/* Statistics Cards */} - <div className="stats-grid"> - <div className="card stat-card"> - <div className="stat-icon"> - <FaCalendarCheck size={24} /> - </div> - <div className="stat-content"> - <div className="stat-value">{stats.totalWorkouts}</div> - <div className="stat-label">Total Workouts</div> - </div> - </div> - - <div className="card stat-card"> - <div className="stat-icon"> - <FaClock size={24} /> - </div> - <div className="stat-content"> - <div className="stat-value">{stats.totalMinutes}</div> - <div className="stat-label">Total Minutes</div> - </div> - </div> - - <div className="card stat-card"> - <div className="stat-icon"> - <FaDumbbell size={24} /> - </div> - <div className="stat-content"> - <div className="stat-value">{stats.totalExercises}</div> - <div className="stat-label">Exercises Done</div> - </div> - </div> - </div> - - {/* Favorite Data */} - {(stats.mostFrequentExercise || stats.mostFrequentRoutine) && ( - <div className="card mb-lg"> - <h2>Your Favorites</h2> - {stats.mostFrequentRoutine && ( - <div className="favorite-item"> - <div className="favorite-label">Most Used Routine:</div> - <div className="favorite-value">{stats.mostFrequentRoutine.name} ({stats.mostFrequentRoutine.count}x)</div> - </div> - )} - {stats.mostFrequentExercise && ( - <div className="favorite-item"> - <div className="favorite-label">Most Performed Exercise:</div> - <div className="favorite-value">{stats.mostFrequentExercise.name} ({stats.mostFrequentExercise.count}x)</div> - </div> - )} - </div> - )} - - {/* Recent Workouts */} - <h2>Recent Workouts</h2> - {stats.recentWorkouts && stats.recentWorkouts.length > 0 ? ( - stats.recentWorkouts.map((workout: RecordRoutine) => ( - <div key={workout.id} className="card workout-card"> - <div className="workout-header"> - <h3>{workout.routine?.name || 'Workout'}</h3> - <div className="workout-date">{formatDate(workout.startedAt)}</div> - </div> - {workout.endedAt && ( - <div className="workout-duration"> - Duration: {calculateDuration(workout.startedAt, workout.endedAt)} - </div> - )} - <div className="workout-exercises"> - </div> - </div> - )) - ) : ( - <div className="empty-state"> - <p>No workouts recorded yet. Start your fitness journey today!</p> - <a href="/new-workout" className="btn btn-primary mt-md">Record a Workout</a> - </div> - )} - - <style>{` - .stats-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: var(--spacing-md); - margin-bottom: var(--spacing-lg); - } - - .stat-card { - display: flex; - align-items: center; - } - - .stat-icon { - display: flex; - align-items: center; - justify-content: center; - width: 50px; - height: 50px; - background-color: rgba(0, 122, 255, 0.1); - border-radius: 50%; - color: var(--primary-color); - margin-right: var(--spacing-md); - } - - .stat-value { - font-size: 24px; - font-weight: bold; - } - - .stat-label { - color: var(--dark-gray); - } - - .favorite-item { - display: flex; - justify-content: space-between; - padding: var(--spacing-sm) 0; - border-bottom: 1px solid var(--light-gray); - } - - .favorite-item:last-child { - border-bottom: none; - } - - .favorite-label { - color: var(--dark-gray); - } - - .workout-card { - margin-bottom: var(--spacing-md); - } - - .workout-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--spacing-sm); - } - - .workout-date { - color: var(--dark-gray); - font-size: 0.9rem; - } - - .workout-duration, .workout-exercises, .workout-notes { - margin-bottom: var(--spacing-sm); - } - - .workout-feeling { - color: var(--warning-color); - } - - .empty-state { - text-align: center; - padding: var(--spacing-xl); - } - `}</style> - </div> - ); -}; - -export default HomePage;
@@ -1,400 +0,0 @@
-import { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { FaSave, FaArrowLeft, FaTrash, FaTimes } from 'react-icons/fa'; -import type { Exercise, Equipment, MuscleGroup } from '../types/models'; -import { ExerciseService, EquipmentService, MuscleGroupService } from '../services/api'; - -const NewExercisePage = () => { - const navigate = useNavigate(); - const location = useLocation(); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [error, setError] = useState<string | null>(null); - const [successMessage, setSuccessMessage] = useState<string | null>(null); - - const [exerciseToEdit, setExerciseToEdit] = useState<Exercise | null>(null); - const [isEditMode, setIsEditMode] = useState<boolean>(false); - - // Available equipment and muscle groups from API - const [availableEquipment, setAvailableEquipment] = useState<Equipment[]>([]); - const [availableMuscleGroups, setAvailableMuscleGroups] = useState<MuscleGroup[]>([]); - - // Selected items - const [selectedEquipment, setSelectedEquipment] = useState<Equipment[]>([]); - const [selectedMuscleGroups, setSelectedMuscleGroups] = useState<MuscleGroup[]>([]); - - // Form state - const [formData, setFormData] = useState<Exercise>({ - name: '', - description: '', - equipment: [], - muscleGroups: [], - sets: [], - }); - - // Fetch equipment and muscle groups from API - useEffect(() => { - const fetchData = async () => { - setIsLoading(true); - try { - const [equipmentData, muscleGroupsData] = await Promise.all([ - EquipmentService.getAll(), - MuscleGroupService.getAll() - ]); - setAvailableEquipment(equipmentData); - setAvailableMuscleGroups(muscleGroupsData); - } catch (err) { - console.error('Failed to fetch data:', err); - setError('Failed to load equipment and muscle groups'); - } finally { - setIsLoading(false); - } - }; - - fetchData(); - }, []); - - // Check if we're editing an existing exercise - useEffect(() => { - if (location.state && location.state.editExercise) { - const exercise = location.state.editExercise as Exercise; - setFormData(exercise); - setSelectedEquipment(exercise.equipment || []); - setSelectedMuscleGroups(exercise.muscleGroups || []); - setExerciseToEdit(exercise); - setIsEditMode(true); - } - }, [location]); - - const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { - const { name, value } = e.target; - - setFormData(prev => ({ - ...prev, - [name]: value, - })); - }; - - const handleEquipmentSelect = (e: React.ChangeEvent<HTMLSelectElement>) => { - const equipmentId = parseInt(e.target.value); - const equipment = availableEquipment.find(eq => eq.id === equipmentId); - - if (equipment && !selectedEquipment.some(e => e.id === equipment.id)) { - const updatedEquipment = [...selectedEquipment, equipment]; - setSelectedEquipment(updatedEquipment); - setFormData(prev => ({ - ...prev, - equipment: updatedEquipment - })); - } - - // Reset select to default - e.target.value = ''; - }; - - const removeEquipment = (equipmentId?: number) => { - if (!equipmentId) return; - const updatedEquipment = selectedEquipment.filter(e => e.id !== equipmentId); - setSelectedEquipment(updatedEquipment); - setFormData(prev => ({ - ...prev, - equipment: updatedEquipment - })); - }; - - const handleMuscleGroupSelect = (e: React.ChangeEvent<HTMLSelectElement>) => { - const muscleGroupId = parseInt(e.target.value); - const muscleGroup = availableMuscleGroups.find(mg => mg.id === muscleGroupId); - - if (muscleGroup && !selectedMuscleGroups.some(mg => mg.id === muscleGroup.id)) { - const updatedMuscleGroups = [...selectedMuscleGroups, muscleGroup]; - setSelectedMuscleGroups(updatedMuscleGroups); - setFormData(prev => ({ - ...prev, - muscleGroups: updatedMuscleGroups - })); - } - - // Reset select to default - e.target.value = ''; - }; - - const removeMuscleGroup = (muscleGroupId?: number) => { - if (!muscleGroupId) return; - const updatedMuscleGroups = selectedMuscleGroups.filter(mg => mg.id !== muscleGroupId); - setSelectedMuscleGroups(updatedMuscleGroups); - setFormData(prev => ({ - ...prev, - muscleGroups: updatedMuscleGroups - })); - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsLoading(true); - setError(null); - - try { - if (formData.muscleGroups.length === 0) { - throw new Error('Please select at least one muscle group'); - } - - if (isEditMode && exerciseToEdit?.id) { - // Update existing exercise - await ExerciseService.update(exerciseToEdit.id, formData); - setSuccessMessage('Exercise updated successfully!'); - } else { - // Create new exercise - await ExerciseService.create(formData); - setSuccessMessage('Exercise created successfully!'); - - // Reset form if creating new - if (!isEditMode) { - setFormData({ - name: '', - description: '', - equipment: [], - muscleGroups: [], - sets: [], - }); - setSelectedEquipment([]); - setSelectedMuscleGroups([]); - } - } - - // Show success message briefly then redirect - setTimeout(() => { - navigate('/workouts'); - }, 1500); - } catch (err: unknown) { - console.error('Failed to save exercise:', err); - setError('Failed to save exercise. Please try again.'); - } finally { - setIsLoading(false); - } - }; - - const handleDelete = async () => { - if (!exerciseToEdit?.id || !confirm('Are you sure you want to delete this exercise?')) { - return; - } - - setIsLoading(true); - try { - await ExerciseService.delete(exerciseToEdit.id); - setSuccessMessage('Exercise deleted successfully!'); - - // Redirect after deletion - setTimeout(() => { - navigate('/workouts'); - }, 1500); - } catch (err) { - console.error('Failed to delete exercise:', err); - setError('Failed to delete exercise. It might be used in one or more routines.'); - } finally { - setIsLoading(false); - } - }; - - return ( - <div className="page new-exercise-page"> - <div className="page-header"> - <button - onClick={() => navigate(-1)} - className="btn btn-secondary back-button" - > - <FaArrowLeft /> Back - </button> - <h1>{isEditMode ? 'Edit Exercise' : 'New Exercise'}</h1> - </div> - - {error && <div className="error-message">{error}</div>} - {successMessage && <div className="success-message">{successMessage}</div>} - - <div className="card"> - <form onSubmit={handleSubmit}> - <div className="form-group"> - <label htmlFor="name">Exercise Name*</label> - <input - type="text" - id="name" - name="name" - value={formData.name} - onChange={handleInputChange} - placeholder="e.g. Bench Press" - required - disabled={isLoading} - /> - </div> - - <div className="form-group"> - <label htmlFor="description">Description</label> - <textarea - id="description" - name="description" - value={formData.description} - onChange={handleInputChange} - placeholder="Describe how to perform this exercise properly..." - rows={4} - disabled={isLoading} - /> - </div> - - <div className="form-group"> - <label htmlFor="muscleGroups">Muscle Groups*</label> - <select - id="muscleGroups" - name="muscleGroups" - onChange={handleMuscleGroupSelect} - disabled={isLoading} - > - <option value="">Select muscle group...</option> - {availableMuscleGroups.map(group => ( - <option key={group.id} value={group.id}>{group.name}</option> - ))} - </select> - - <div className="tags-container"> - {selectedMuscleGroups.map(group => ( - <div key={group.id} className="tag"> - {group.name} - <button - type="button" - className="tag-remove" - onClick={() => removeMuscleGroup(group.id)} - disabled={isLoading} - > - <FaTimes /> - </button> - </div> - ))} - </div> - </div> - - <div className="form-group"> - <label htmlFor="equipment">Equipment</label> - <select - id="equipment" - name="equipment" - onChange={handleEquipmentSelect} - disabled={isLoading} - > - <option value="">Select equipment...</option> - {availableEquipment.map(eq => ( - <option key={eq.id} value={eq.id}>{eq.name}</option> - ))} - </select> - - <div className="tags-container"> - {selectedEquipment.map(eq => ( - <div key={eq.id} className="tag"> - {eq.name} - <button - type="button" - className="tag-remove" - onClick={() => removeEquipment(eq.id)} - disabled={isLoading} - > - <FaTimes /> - </button> - </div> - ))} - </div> - </div> - - <div className="form-actions"> - <button - type="submit" - className="btn btn-primary btn-block" - disabled={isLoading} - > - <FaSave /> {isEditMode ? 'Update Exercise' : 'Save Exercise'} - </button> - - {isEditMode && exerciseToEdit?.id && ( - <button - type="button" - className="btn btn-danger btn-block mt-md" - onClick={handleDelete} - disabled={isLoading} - > - <FaTrash /> Delete Exercise - </button> - )} - </div> - </form> - </div> - - <style>{` - .page-header { - display: flex; - align-items: center; - margin-bottom: var(--spacing-lg); - } - - .back-button { - margin-right: var(--spacing-md); - padding: var(--spacing-sm) var(--spacing-md); - } - - .form-actions { - margin-top: var(--spacing-xl); - } - - textarea { - resize: vertical; - } - - .error-message { - background-color: rgba(255, 59, 48, 0.1); - color: var(--danger-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - - .success-message { - background-color: rgba(52, 199, 89, 0.1); - color: var(--success-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - - .tags-container { - display: flex; - flex-wrap: wrap; - gap: var(--spacing-sm); - margin-top: var(--spacing-sm); - } - - .tag { - display: flex; - align-items: center; - background-color: var(--primary-color-light); - color: var(--primary-color-dark); - padding: var(--spacing-xs) var(--spacing-sm); - border-radius: var(--border-radius); - font-size: 0.9rem; - } - - .tag-remove { - border: none; - background: none; - color: var(--primary-color-dark); - margin-left: var(--spacing-xs); - padding: 0; - font-size: 0.8rem; - display: flex; - align-items: center; - cursor: pointer; - } - - .mt-md { - margin-top: var(--spacing-md); - } - `}</style> - </div> - ); -}; - -export default NewExercisePage;
@@ -1,655 +0,0 @@
-import { useState, useEffect } from 'react'; -import { useNavigate, useLocation, Link } from 'react-router-dom'; -import { - FaPlus, - FaSave, - FaArrowLeft, - FaTrash, - FaArrowUp, - FaArrowDown, - FaFilter, -} from 'react-icons/fa'; -import type { Routine, Exercise, RoutineItem } from '../types/models'; -import { RoutineService, ExerciseService } from '../services/api'; - -const NewRoutinePage = () => { - const navigate = useNavigate(); - const location = useLocation(); - - // State for exercises - const [exercises, setExercises] = useState<Exercise[]>([]); - const [selectedItems, setSelectedItems] = useState<RoutineItem[]>([]); - const [searchTerm, setSearchTerm] = useState<string>(''); - const [muscleFilter, setMuscleFilter] = useState<string>(''); - - // State for routine data - const [routineName, setRoutineName] = useState<string>(''); - const [routineDescription, setRoutineDescription] = useState<string>(''); - const [estimatedDuration, setEstimatedDuration] = useState<number>(0); - - // UI state - const [isLoading, setIsLoading] = useState<boolean>(true); - const [isSaving, setIsSaving] = useState<boolean>(false); - const [error, setError] = useState<string | null>(null); - const [successMessage, setSuccessMessage] = useState<string | null>(null); - - // Track if we're editing an existing routine - const [isEditMode, setIsEditMode] = useState<boolean>(false); - const [routineToEdit, setRoutineToEdit] = useState<Routine | null>(null); - - // Fetch available exercises and check if we're in edit mode - useEffect(() => { - const fetchExercises = async () => { - try { - setIsLoading(true); - const data = await ExerciseService.getAll(); - setExercises(data); - - // Check if we're editing an existing routine - if (location.state && location.state.editRoutine) { - const routine = location.state.editRoutine as Routine; - setRoutineName(routine.name); - setRoutineDescription(routine.description); - - // Set selected items from the routine - if (routine.routineItems && routine.routineItems.length > 0) { - setSelectedItems(routine.routineItems); - } - - setRoutineToEdit(routine); - setIsEditMode(true); - } - - setError(null); - } catch (err) { - console.error('Failed to fetch exercises:', err); - setError('Could not load exercises. Please try again later.'); - } finally { - setIsLoading(false); - } - }; - - fetchExercises(); - }, [location]); - - // Find unique muscle groups for filtering - const muscleGroups = [...new Set(exercises.flatMap(ex => - ex.muscleGroups.map(mg => mg.name) - ))].sort(); - - // Filter exercises based on search and muscle filter - const filteredExercises = exercises.filter(ex => { - const matchesSearch = ex.name.toLowerCase().includes(searchTerm.toLowerCase()) || - ex.description.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesMuscle = !muscleFilter || - ex.muscleGroups.some(mg => mg.name === muscleFilter); - return matchesSearch && matchesMuscle; - }); - - // Handle adding an exercise to the routine - const handleAddExercise = (exercise: Exercise) => { - const newItem: RoutineItem = { - routineId: routineToEdit?.id || 0, - exerciseId: exercise.id, - superSetId: null, - restTime: 60, // Default rest time 60 seconds - orderIndex: selectedItems.length, - exercise: exercise, - superSet: null, - }; - - setSelectedItems([...selectedItems, newItem]); - }; - - // Handle removing an exercise from the routine - const handleRemoveItem = (index: number) => { - const newSelectedItems = [...selectedItems]; - newSelectedItems.splice(index, 1); - - // Update order values - const reorderedItems = newSelectedItems.map((item, i) => ({ - ...item, - orderIndex: i, - })); - - setSelectedItems(reorderedItems); - }; - - // Handle updating exercise details - const handleItemChange = (index: number, field: string, value: any) => { - const newSelectedItems = [...selectedItems]; - // @ts-ignore - newSelectedItems[index][field] = value; - setSelectedItems(newSelectedItems); - }; - - // Handle moving exercises up/down in the order - const handleMoveItem = (index: number, direction: 'up' | 'down') => { - if ( - (direction === 'up' && index === 0) || - (direction === 'down' && index === selectedItems.length - 1) - ) { - return; - } - - const newSelectedItems = [...selectedItems]; - const swapIndex = direction === 'up' ? index - 1 : index + 1; - - // Swap items - [newSelectedItems[index], newSelectedItems[swapIndex]] = - [newSelectedItems[swapIndex], newSelectedItems[index]]; - - // Update order values - const reorderedItems = newSelectedItems.map((item, i) => ({ - ...item, - orderIndex: i, - })); - - setSelectedItems(reorderedItems); - }; - - // Calculate estimated duration based on exercises and rest times - useEffect(() => { - let totalMinutes = 0; - - selectedItems.forEach(item => { - if (item.exercise) { - // Estimate time for each set (1 min per set) + rest time - const setCount = item.exercise.sets?.length || 3; // Default to 3 if no sets defined - const restTime = item.restTime / 60; // Convert seconds to minutes - totalMinutes += setCount + restTime; - } else if (item.superSet) { - // For supersets, account for both exercises - const primarySets = item.superSet.primaryExercise?.sets?.length || 3; - const secondarySets = item.superSet.secondaryExercise?.sets?.length || 3; - const restTime = item.superSet.restTime / 60; - totalMinutes += primarySets + secondarySets + restTime; - } - }); - - // Add some buffer time for transitions between exercises - totalMinutes += selectedItems.length > 0 ? Math.ceil(selectedItems.length / 3) : 0; - - setEstimatedDuration(Math.ceil(totalMinutes)); - }, [selectedItems]); - - // Handle saving the routine - const handleSaveRoutine = async (e: React.FormEvent) => { - e.preventDefault(); - - if (selectedItems.length === 0) { - setError('Please add at least one exercise to your routine.'); - return; - } - - setIsSaving(true); - setError(null); - - // Prepare the routineItems by removing circular references - const sanitizedItems = selectedItems.map(item => { - const { exercise, superSet, ...rest } = item; - return rest; - }); - - const routineData: Routine = { - name: routineName, - description: routineDescription, - routineItems: sanitizedItems, - }; - - try { - if (isEditMode && routineToEdit?.id) { - // Update existing routine - await RoutineService.update(routineToEdit.id, routineData); - setSuccessMessage('Routine updated successfully!'); - } else { - // Create new routine - await RoutineService.create(routineData); - setSuccessMessage('Routine created successfully!'); - } - - // Show success message briefly then redirect - setTimeout(() => { - navigate('/workouts'); - }, 1500); - } catch (err) { - console.error('Failed to save routine:', err); - setError('Failed to save routine. Please try again.'); - } finally { - setIsSaving(false); - } - }; - - return ( - <div className="page new-routine-page"> - <div className="page-header"> - <button - onClick={() => navigate(-1)} - className="btn btn-secondary back-button" - > - <FaArrowLeft /> Back - </button> - <h1>{isEditMode ? 'Edit Routine' : 'Create Routine'}</h1> - </div> - - {error && <div className="error-message">{error}</div>} - {successMessage && <div className="success-message">{successMessage}</div>} - - {isLoading ? ( - <div className="loading">Loading exercises...</div> - ) : ( - <div className="routine-builder"> - <div className="routine-details card"> - <h2>Routine Details</h2> - <form onSubmit={handleSaveRoutine}> - <div className="form-group"> - <label htmlFor="routineName">Routine Name*</label> - <input - type="text" - id="routineName" - value={routineName} - onChange={(e) => setRoutineName(e.target.value)} - placeholder="e.g. Upper Body Strength" - required - disabled={isSaving} - /> - </div> - - <div className="form-group"> - <label htmlFor="routineDescription">Description</label> - <textarea - id="routineDescription" - value={routineDescription} - onChange={(e) => setRoutineDescription(e.target.value)} - placeholder="Describe your routine..." - rows={3} - disabled={isSaving} - /> - </div> - - <div className="routine-summary"> - <div className="summary-item"> - <span className="summary-label">Exercises:</span> - <span className="summary-value">{selectedItems.length}</span> - </div> - <div className="summary-item"> - <span className="summary-label">Est. Duration:</span> - <span className="summary-value">{estimatedDuration} min</span> - </div> - </div> - - <div className="selected-exercises"> - <h3>Selected Exercises</h3> - - {selectedItems.length === 0 ? ( - <div className="empty-state"> - <p>No exercises added yet.</p> - <p className="hint">Select exercises from the list below to add them to your routine.</p> - </div> - ) : ( - <div className="exercise-list"> - {selectedItems.map((item, index) => ( - <div key={`${index}-${item.exerciseId || item.superSetId}`} className="selected-exercise-item"> - <div className="exercise-order">#{index + 1}</div> - - <div className="exercise-content"> - <div className="exercise-name">{item.exercise?.name || (item.superSet ? `${item.superSet.primaryExercise.name} + ${item.superSet.secondaryExercise.name}` : "Unknown Exercise")}</div> - - <div className="exercise-details"> - <div className="detail-item"> - <label htmlFor={`rest-${index}`}>Rest (sec):</label> - <input - id={`rest-${index}`} - type="number" - min="0" - max="300" - step="15" - value={item.restTime} - onChange={(e) => handleItemChange(index, 'restTime', parseInt(e.target.value))} - disabled={isSaving} - /> - </div> - </div> - </div> - - <div className="exercise-actions"> - <button - type="button" - onClick={() => handleMoveItem(index, 'up')} - disabled={index === 0 || isSaving} - className="btn btn-secondary action-btn" - title="Move up" - > - <FaArrowUp /> - </button> - <button - type="button" - onClick={() => handleMoveItem(index, 'down')} - disabled={index === selectedItems.length - 1 || isSaving} - className="btn btn-secondary action-btn" - title="Move down" - > - <FaArrowDown /> - </button> - <button - type="button" - onClick={() => handleRemoveItem(index)} - disabled={isSaving} - className="btn btn-danger action-btn" - title="Remove" - > - <FaTrash /> - </button> - </div> - </div> - ))} - </div> - )} - </div> - - <div className="form-actions"> - <button - type="submit" - className="btn btn-primary btn-block" - disabled={isSaving || selectedItems.length === 0 || !routineName} - > - <FaSave /> {isEditMode ? 'Update Routine' : 'Save Routine'} - </button> - </div> - </form> - </div> - - <div className="exercise-picker card"> - <h2>Available Exercises</h2> - - <div className="exercise-filters"> - <div className="search-input-container"> - <FaFilter /> - <input - type="text" - placeholder="Search exercises..." - value={searchTerm} - onChange={(e) => setSearchTerm(e.target.value)} - className="search-input" - /> - </div> - - <div className="muscle-filter"> - <select - value={muscleFilter} - onChange={(e) => setMuscleFilter(e.target.value)} - > - <option value="">All Muscle Groups</option> - {muscleGroups.map(group => ( - <option key={group} value={group}>{group}</option> - ))} - </select> - </div> - </div> - - {filteredExercises.length === 0 && ( - <div className="empty-state"> - <p>No exercises found.</p> - <Link to="/new-exercise" className="btn btn-primary mt-md"> - <FaPlus /> Create New Exercise - </Link> - </div> - )} - - <div className="available-exercises"> - {filteredExercises.map(exercise => ( - <div key={exercise.id} className="exercise-item"> - <div className="exercise-info"> - <h4>{exercise.name}</h4> - <div className="exercise-metadata"> - <span>{exercise.muscleGroups.map(mg => mg.name).join(', ')}</span> - <span>{exercise.equipment.map(eq => eq.name).join(', ')}</span> - </div> - {exercise.description && ( - <div className="exercise-description">{exercise.description}</div> - )} - </div> - <button - type="button" - onClick={() => handleAddExercise(exercise)} - className="btn btn-primary" - disabled={isSaving} - > - <FaPlus /> Add - </button> - </div> - ))} - </div> - - <div className="text-center mt-lg"> - <Link to="/new-exercise" className="btn btn-secondary"> - <FaPlus /> Create New Exercise - </Link> - </div> - </div> - </div> - )} - - <style>{` - .page-header { - display: flex; - align-items: center; - margin-bottom: var(--spacing-lg); - } - - .back-button { - margin-right: var(--spacing-md); - padding: var(--spacing-sm) var(--spacing-md); - } - - .routine-builder { - display: grid; - gap: var(--spacing-lg); - } - - @media (min-width: 768px) { - .routine-builder { - grid-template-columns: 1fr 1fr; - } - } - - .routine-summary { - display: flex; - gap: var(--spacing-lg); - margin: var(--spacing-md) 0; - } - - .summary-item { - padding: var(--spacing-sm) var(--spacing-md); - background-color: rgba(0, 122, 255, 0.1); - border-radius: var(--border-radius); - display: flex; - align-items: center; - gap: var(--spacing-sm); - } - - .summary-label { - font-weight: 500; - } - - .selected-exercises { - margin-top: var(--spacing-lg); - } - - h3 { - margin-bottom: var(--spacing-md); - } - - .selected-exercise-item { - display: flex; - align-items: center; - padding: var(--spacing-md); - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-sm); - background-color: white; - } - - .exercise-order { - font-weight: bold; - width: 30px; - height: 30px; - border-radius: 50%; - background-color: var(--primary-color); - color: white; - display: flex; - align-items: center; - justify-content: center; - margin-right: var(--spacing-md); - } - - .exercise-content { - flex: 1; - } - - .exercise-name { - font-weight: 600; - margin-bottom: var(--spacing-xs); - } - - .exercise-details { - display: flex; - flex-wrap: wrap; - gap: var(--spacing-md); - } - - .detail-item { - display: flex; - align-items: center; - gap: var(--spacing-xs); - } - - .detail-item input { - width: 60px; - padding: 4px; - text-align: center; - } - - .exercise-actions { - display: flex; - gap: var(--spacing-xs); - } - - .action-btn { - padding: 5px; - font-size: 0.8rem; - } - - .exercise-filters { - display: flex; - gap: var(--spacing-md); - margin-bottom: var(--spacing-md); - } - - .search-input-container { - position: relative; - flex: 1; - } - - .search-input-container .fa-filter { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - color: var(--text-muted); - } - - .search-input { - padding-left: 30px; - width: 100%; - } - - .available-exercises { - max-height: 500px; - overflow-y: auto; - } - - .exercise-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--spacing-md); - border-bottom: 1px solid var(--border-color); - } - - .exercise-info { - flex: 1; - } - - .exercise-info h4 { - margin: 0; - margin-bottom: var(--spacing-xs); - } - - .exercise-metadata { - font-size: 0.85rem; - color: var(--text-muted); - display: flex; - gap: var(--spacing-md); - margin-bottom: var(--spacing-xs); - } - - .exercise-description { - font-size: 0.9rem; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - } - - .empty-state { - padding: var(--spacing-lg); - text-align: center; - color: var(--text-muted); - } - - .hint { - font-size: 0.9rem; - margin-top: var(--spacing-md); - } - - .mt-md { - margin-top: var(--spacing-md); - } - - .mt-lg { - margin-top: var(--spacing-lg); - } - - .text-center { - text-align: center; - } - - .loading { - text-align: center; - padding: var(--spacing-lg); - color: var(--text-muted); - } - - .error-message { - background-color: rgba(255, 59, 48, 0.1); - color: var(--danger-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - - .success-message { - background-color: rgba(52, 199, 89, 0.1); - color: var(--success-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - `}</style> - </div> - ); -}; - -export default NewRoutinePage;
@@ -1,1088 +0,0 @@
-import { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { - FaArrowLeft, - FaCheck, - FaSave, - FaPlay, - FaStop, - FaStar, - FaRegStar, - FaForward -} from 'react-icons/fa'; -import type { - Routine, - Exercise, - RecordRoutine, - RecordSet, - Set, - RecordItem -} from '../types/models'; -import { routineService } from '../services/api'; - -interface SetForWorkout { - id?: number; - setId: number; - actualReps: number; - actualWeight: number; - actualDuration: number; - originalSet: Set; - completed: boolean; -} - -interface ExerciseForWorkout { - id?: number; - exerciseId: number; - exercise: Exercise; - sets: SetForWorkout[]; - startedAt?: string; - endedAt?: string; - actualRestTime: number; // in seconds - notes: string; -} - -const NewWorkoutPage = () => { - const navigate = useNavigate(); - const location = useLocation(); - - // Routines state - const [routines, setRoutines] = useState<Routine[]>([]); - const [selectedRoutine, setSelectedRoutine] = useState<Routine | null>(null); - const [isLoading, setIsLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - - // Workout tracking state - const [workoutStarted, setWorkoutStarted] = useState<boolean>(false); - const [workoutCompleted, setWorkoutCompleted] = useState<boolean>(false); - const [startTime, setStartTime] = useState<string>(''); - const [endTime, setEndTime] = useState<string | null>(null); - const [elapsedSeconds, setElapsedSeconds] = useState<number>(0); - const [intervalId, setIntervalId] = useState<number | null>(null); - - // Exercise tracking state - const [currentExerciseIndex, setCurrentExerciseIndex] = useState<number>(0); - const [workoutExercises, setWorkoutExercises] = useState<ExerciseForWorkout[]>([]); - - // Workout notes and rating - const [workoutNotes, setWorkoutNotes] = useState<string>(''); - const [feelingRating, setFeelingRating] = useState<number>(3); - - // Success message state - const [successMessage, setSuccessMessage] = useState<string | null>(null); - - // Load routines and check for pre-selected routine - useEffect(() => { - const fetchRoutines = async () => { - try { - setIsLoading(true); - const data = await routineService.getAll(); - setRoutines(data); - - // Check if a routine was pre-selected (from workouts page) - if (location.state && location.state.routineId) { - const routineId = location.state.routineId; - const routine = data.find(r => r.id === routineId); - - if (routine) { - handleSelectRoutine(routine); - } - } - - setError(null); - } catch (err) { - console.error('Failed to fetch routines:', err); - setError('Could not load workout routines. Please try again later.'); - } finally { - setIsLoading(false); - } - }; - - fetchRoutines(); - }, [location]); - - // Setup the workout when a routine is selected - const handleSelectRoutine = (routine: Routine) => { - setSelectedRoutine(routine); - - // Initialize workout exercises from routine items - const exercises: ExerciseForWorkout[] = []; - - // Process routine items into exercises for the workout - routine.items.forEach(item => { - if (item.exerciseItems && item.exerciseId) { - // This is a regular exercise item - const exercise = item.exercise; - - // Get the sets from the exercise or create default ones - const exerciseSets = exercise.sets || []; - const setsForWorkout: SetForWorkout[] = exerciseSets.map(set => ({ - setId: set.id || 0, - originalSet: set, - actualReps: set.reps, - actualWeight: set.weight, - actualDuration: set.duration, - completed: false - })); - - // If there are no sets defined, create a default set - if (setsForWorkout.length === 0) { - setsForWorkout.push({ - setId: 0, - originalSet: { - id: 0, - exerciseId: exercise.id || 0, - reps: 10, - weight: 0, - duration: 0, - orderIndex: 0 - }, - actualReps: 10, - actualWeight: 0, - actualDuration: 0, - completed: false - }); - } - - exercises.push({ - exerciseId: exercise.id || 0, - exercise: exercise, - sets: setsForWorkout, - actualRestTime: item.restTime, - notes: '' - }); - } - // We could handle supersets here if needed - }); - - setWorkoutExercises(exercises); - setCurrentExerciseIndex(0); - }; - - // Start the workout - const startWorkout = () => { - if (!selectedRoutine) return; - - const now = new Date().toISOString(); - setStartTime(now); - setWorkoutStarted(true); - - // Mark first exercise as started - if (workoutExercises.length > 0) { - const updatedExercises = [...workoutExercises]; - updatedExercises[0].startedAt = now; - setWorkoutExercises(updatedExercises); - } - - // Start the timer - const id = window.setInterval(() => { - setElapsedSeconds(prev => prev + 1); - }, 1000); - - setIntervalId(id); - }; - - // Complete the workout - const completeWorkout = () => { - if (intervalId) { - clearInterval(intervalId); - setIntervalId(null); - } - - const now = new Date().toISOString(); - setEndTime(now); - - // Mark current exercise as completed if not already - if (workoutExercises.length > 0 && currentExerciseIndex < workoutExercises.length) { - const updatedExercises = [...workoutExercises]; - const currentExercise = updatedExercises[currentExerciseIndex]; - - if (currentExercise.startedAt && !currentExercise.endedAt) { - currentExercise.endedAt = now; - } - - setWorkoutExercises(updatedExercises); - } - - setWorkoutCompleted(true); - }; - - // Format timer display - const formatTime = (seconds: number) => { - const hrs = Math.floor(seconds / 3600); - const mins = Math.floor((seconds % 3600) / 60); - const secs = seconds % 60; - - return `${hrs > 0 ? hrs + ':' : ''}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; - }; - - // Handle set completion toggle - const toggleSetCompleted = (exerciseIndex: number, setIndex: number) => { - const updatedExercises = [...workoutExercises]; - const currentSet = updatedExercises[exerciseIndex].sets[setIndex]; - currentSet.completed = !currentSet.completed; - - setWorkoutExercises(updatedExercises); - }; - - // Handle weight, reps or duration change - const handleSetDataChange = ( - exerciseIndex: number, - setIndex: number, - field: 'actualReps' | 'actualWeight' | 'actualDuration', - value: number - ) => { - const updatedExercises = [...workoutExercises]; - const currentSet = updatedExercises[exerciseIndex].sets[setIndex]; - currentSet[field] = value; - - setWorkoutExercises(updatedExercises); - }; - - // Move to next exercise - const nextExercise = () => { - if (currentExerciseIndex >= workoutExercises.length - 1) return; - - const now = new Date().toISOString(); - const updatedExercises = [...workoutExercises]; - - // Complete current exercise - const currentExercise = updatedExercises[currentExerciseIndex]; - if (currentExercise.startedAt && !currentExercise.endedAt) { - currentExercise.endedAt = now; - } - - // Start next exercise - const nextIndex = currentExerciseIndex + 1; - const nextExercise = updatedExercises[nextIndex]; - nextExercise.startedAt = now; - - setWorkoutExercises(updatedExercises); - setCurrentExerciseIndex(nextIndex); - }; - - // Handle notes for an exercise - const handleExerciseNotes = (exerciseIndex: number, notes: string) => { - const updatedExercises = [...workoutExercises]; - updatedExercises[exerciseIndex].notes = notes; - - setWorkoutExercises(updatedExercises); - }; - - // Create RecordSets from workout exercise sets - const createRecordSets = (exercise: ExerciseForWorkout): RecordSet[] => { - return exercise.sets.map((set, index) => ({ - recordExerciseId: 0, // Will be filled in by backend - setId: set.setId, - actualReps: set.actualReps, - actualWeight: set.actualWeight, - actualDuration: set.actualDuration, - completedAt: exercise.endedAt || new Date().toISOString(), - orderIndex: index, - set: set.originalSet - })); - }; - - // Save workout record - const saveWorkout = async () => { - if (!selectedRoutine || !startTime) return; - - try { - const now = new Date().toISOString(); - - // Ensure all exercises have start/end times - const completedExercises = workoutExercises.map((ex) => { - if (!ex.startedAt) { - ex.startedAt = startTime; - } - if (!ex.endedAt) { - ex.endedAt = endTime || now; - } - return ex; - }); - - // Create RecordExercises from completed exercises - const recordExercises: RecordExercise[] = completedExercises.map((ex, index) => ({ - id: undefined, - recordRoutineId: 0, // Will be filled in by backend - exerciseId: ex.exerciseId, - startedAt: ex.startedAt || startTime, - endedAt: ex.endedAt || now, - actualRestTime: ex.actualRestTime, - orderIndex: index, - recordSets: createRecordSets(ex), - exercise: ex.exercise - })); - - // Create RecordRoutineItems from recordExercises - const recordItems: RecordItem[] = recordExercises.map((ex, index) => ({ - recordRoutineId: 0, // Will be filled in by backend - recordExerciseId: undefined, // Will be filled in after recordExercise is created - recordSuperSetId: null, - actualRestTime: workoutExercises[index].actualRestTime, - orderIndex: index, - recordExercise: ex, - recordSuperSet: null - })); - - const workoutRecord: RecordRoutine = { - routineId: selectedRoutine.id!, - duration: elapsedSeconds, - routine: selectedRoutine, - recordItems: recordItems - }; - - await WorkoutService.create(workoutRecord); - setSuccessMessage('Workout saved successfully!'); - - // Redirect after a brief delay - setTimeout(() => { - navigate('/home'); - }, 1500); - } catch (err) { - console.error('Failed to save workout:', err); - setError('Failed to save your workout. Please try again.'); - } - }; - - // Check if all sets in current exercise are completed - const isCurrentExerciseComplete = () => { - if (currentExerciseIndex >= workoutExercises.length) return false; - - const currentExercise = workoutExercises[currentExerciseIndex]; - return currentExercise.sets.every(set => set.completed); - }; - - // Progress status percentage - const calculateProgress = () => { - if (workoutExercises.length === 0) return 0; - - const totalSets = workoutExercises.reduce((total, ex) => total + ex.sets.length, 0); - const completedSets = workoutExercises.reduce((total, ex) => { - return total + ex.sets.filter(set => set.completed).length; - }, 0); - - return Math.round((completedSets / totalSets) * 100); - }; - - return ( - <div className="page new-workout-page"> - <div className="page-header"> - <button - onClick={() => navigate(-1)} - className="btn btn-secondary back-button" - > - <FaArrowLeft /> Back - </button> - <h1>New Workout</h1> - </div> - - {error && <div className="error-message">{error}</div>} - {successMessage && <div className="success-message">{successMessage}</div>} - - {!selectedRoutine ? ( - // Routine selection view - <div className="routine-selection card"> - <h2>Select a Routine</h2> - - {isLoading ? ( - <div className="loading">Loading routines...</div> - ) : routines.length === 0 ? ( - <div className="empty-state"> - <p>No routines found.</p> - <p>Create a routine to start working out.</p> - <button - onClick={() => navigate('/new-routine')} - className="btn btn-primary mt-md" - > - Create Routine - </button> - </div> - ) : ( - <div className="routines-list"> - {routines.map(routine => ( - <div key={routine.id} className="routine-item" onClick={() => handleSelectRoutine(routine)}> - <h3>{routine.name}</h3> - {routine.description && <p>{routine.description}</p>} - <div className="routine-meta"> - <span>{routine.routineItems.length} exercises</span> - </div> - </div> - ))} - </div> - )} - </div> - ) : !workoutStarted ? ( - // Workout ready view - <div className="workout-ready card"> - <h2>{selectedRoutine.name}</h2> - {selectedRoutine.description && <p className="routine-description">{selectedRoutine.description}</p>} - - <div className="workout-details"> - <div className="detail-item"> - <span className="detail-label">Exercises:</span> - <span className="detail-value">{workoutExercises.length}</span> - </div> - <div className="detail-item"> - <span className="detail-label">Sets:</span> - <span className="detail-value"> - {workoutExercises.reduce((total, ex) => total + ex.sets.length, 0)} - </span> - </div> - </div> - - <div className="exercise-preview"> - <h3>Exercises</h3> - <ul className="exercise-list"> - {workoutExercises.map((exercise, index) => ( - <li key={`${exercise.exerciseId}-${index}`}> - <div className="exercise-name">{exercise.exercise.name}</div> - <div className="exercise-sets">{exercise.sets.length} sets</div> - </li> - ))} - </ul> - </div> - - <div className="action-buttons"> - <button - className="btn btn-primary btn-lg btn-block" - onClick={startWorkout} - > - <FaPlay /> Start Workout - </button> - <button - className="btn btn-secondary btn-block mt-md" - onClick={() => setSelectedRoutine(null)} - > - Select Different Routine - </button> - </div> - </div> - ) : workoutCompleted ? ( - // Workout complete view - <div className="workout-complete card"> - <div className="workout-summary"> - <h2>Workout Complete!</h2> - - <div className="summary-stats"> - <div className="stat"> - <span className="stat-label">Duration</span> - <span className="stat-value">{formatTime(elapsedSeconds)}</span> - </div> - - <div className="stat"> - <span className="stat-label">Completed</span> - <span className="stat-value">{calculateProgress()}%</span> - </div> - </div> - - <div className="feeling-rating"> - <p>How was your workout?</p> - <div className="stars"> - {[1, 2, 3, 4, 5].map(rating => ( - <button - key={rating} - onClick={() => setFeelingRating(rating)} - className="star-btn" - > - {rating <= feelingRating ? <FaStar /> : <FaRegStar />} - </button> - ))} - </div> - </div> - - <div className="workout-notes form-group"> - <label htmlFor="workout-notes">Workout Notes</label> - <textarea - id="workout-notes" - value={workoutNotes} - onChange={e => setWorkoutNotes(e.target.value)} - placeholder="Add notes about the overall workout..." - rows={3} - /> - </div> - - <div className="action-buttons"> - <button - onClick={saveWorkout} - className="btn btn-primary btn-lg btn-block" - > - <FaSave /> Save Workout - </button> - </div> - </div> - </div> - ) : ( - // Active workout view - <div className="active-workout"> - <div className="workout-header card"> - <h2>{selectedRoutine.name}</h2> - - <div className="workout-timer"> - <div className="timer-value">{formatTime(elapsedSeconds)}</div> - <div className="progress-bar"> - <div - className="progress" - style={{ width: `${calculateProgress()}%` }} - ></div> - </div> - </div> - </div> - - <div className="current-exercise card"> - {currentExerciseIndex < workoutExercises.length ? ( - <> - <h3 className="exercise-name"> - {workoutExercises[currentExerciseIndex].exercise.name} - </h3> - - <div className="exercise-sets"> - <table className="sets-table"> - <thead> - <tr> - <th>Set</th> - <th>Weight</th> - <th>Reps</th> - {workoutExercises[currentExerciseIndex].sets.some(s => s.originalSet.duration > 0) && ( - <th>Time</th> - )} - <th>Done</th> - </tr> - </thead> - <tbody> - {workoutExercises[currentExerciseIndex].sets.map((set, setIndex) => ( - <tr key={setIndex} className={set.completed ? 'completed' : ''}> - <td>{setIndex + 1}</td> - <td> - <input - type="number" - min="0" - step="1" - value={set.actualWeight} - onChange={e => handleSetDataChange( - currentExerciseIndex, - setIndex, - 'actualWeight', - parseFloat(e.target.value) || 0 - )} - /> - </td> - <td> - <input - type="number" - min="0" - step="1" - value={set.actualReps} - onChange={e => handleSetDataChange( - currentExerciseIndex, - setIndex, - 'actualReps', - parseInt(e.target.value) || 0 - )} - /> - </td> - {workoutExercises[currentExerciseIndex].sets.some(s => s.originalSet.duration > 0) && ( - <td> - <input - type="number" - min="0" - step="1" - value={set.actualDuration} - onChange={e => handleSetDataChange( - currentExerciseIndex, - setIndex, - 'actualDuration', - parseInt(e.target.value) || 0 - )} - /> - </td> - )} - <td> - <button - className={`btn-check ${set.completed ? 'completed' : ''}`} - onClick={() => toggleSetCompleted(currentExerciseIndex, setIndex)} - > - {set.completed && <FaCheck />} - </button> - </td> - </tr> - ))} - </tbody> - </table> - </div> - - <div className="exercise-notes form-group"> - <label htmlFor="exercise-notes">Exercise Notes</label> - <textarea - id="exercise-notes" - value={workoutExercises[currentExerciseIndex].notes} - onChange={e => handleExerciseNotes(currentExerciseIndex, e.target.value)} - placeholder="Add notes for this exercise..." - rows={2} - /> - </div> - - <div className="exercise-navigation"> - {currentExerciseIndex < workoutExercises.length - 1 && ( - <button - onClick={nextExercise} - className={`btn btn-primary ${isCurrentExerciseComplete() ? 'pulse' : ''}`} - disabled={!isCurrentExerciseComplete() && workoutExercises[currentExerciseIndex].sets.length > 0} - > - <FaForward /> Next Exercise - </button> - )} - - {currentExerciseIndex === workoutExercises.length - 1 && isCurrentExerciseComplete() && ( - <button - onClick={completeWorkout} - className="btn btn-primary pulse" - > - <FaStop /> Finish Workout - </button> - )} - </div> - </> - ) : ( - <div> - <p>All exercises completed!</p> - <button - onClick={completeWorkout} - className="btn btn-primary" - > - <FaStop /> Finish Workout - </button> - </div> - )} - </div> - - <div className="workout-nav"> - <div className="exercises-list card"> - <h3>Progress</h3> - <ul> - {workoutExercises.map((ex, index) => ( - <li - key={`${ex.exerciseId}-${index}`} - className={` - ${index === currentExerciseIndex ? 'active' : ''} - ${ex.sets.every(s => s.completed) ? 'completed' : ''} - `} - onClick={() => setCurrentExerciseIndex(index)} - > - <span className="exercise-number">{index + 1}</span> - <span className="exercise-list-name">{ex.exercise.name}</span> - <span className="exercise-progress"> - {ex.sets.filter(s => s.completed).length}/{ex.sets.length} - </span> - </li> - ))} - </ul> - </div> - - <div className="workout-actions card"> - <button - onClick={completeWorkout} - className="btn btn-danger btn-block" - > - <FaStop /> End Workout - </button> - </div> - </div> - </div> - )} - - <style>{` - .page-header { - display: flex; - align-items: center; - margin-bottom: var(--spacing-lg); - } - - .back-button { - margin-right: var(--spacing-md); - padding: var(--spacing-sm) var(--spacing-md); - } - - /* Routine Selection */ - .routines-list { - display: grid; - gap: var(--spacing-md); - } - - .routine-item { - padding: var(--spacing-md); - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - cursor: pointer; - transition: all 0.2s; - } - - .routine-item:hover { - background-color: rgba(0, 122, 255, 0.05); - border-color: var(--primary-color); - } - - .routine-item h3 { - margin: 0; - margin-bottom: var(--spacing-xs); - } - - .routine-item p { - margin: 0; - margin-bottom: var(--spacing-sm); - color: var(--text-muted); - } - - .routine-meta { - font-size: 0.9rem; - color: var(--text-muted); - } - - /* Workout Ready */ - .routine-description { - color: var(--text-muted); - margin-bottom: var(--spacing-lg); - } - - .workout-details { - display: flex; - gap: var(--spacing-lg); - margin-bottom: var(--spacing-lg); - } - - .detail-item { - display: flex; - flex-direction: column; - align-items: center; - background-color: rgba(0, 122, 255, 0.1); - padding: var(--spacing-md) var(--spacing-lg); - border-radius: var(--border-radius); - } - - .detail-label { - color: var(--text-muted); - font-size: 0.9rem; - } - - .detail-value { - font-size: 1.2rem; - font-weight: bold; - } - - .exercise-preview { - margin-bottom: var(--spacing-lg); - } - - .exercise-list { - list-style: none; - padding: 0; - margin: 0; - } - - .exercise-list li { - padding: var(--spacing-sm) 0; - border-bottom: 1px solid var(--border-color); - display: flex; - justify-content: space-between; - } - - .exercise-list li:last-child { - border-bottom: none; - } - - .exercise-sets { - color: var(--text-muted); - font-size: 0.9rem; - } - - .btn-lg { - padding: var(--spacing-md); - font-size: 1.1rem; - } - - /* Active Workout */ - .workout-header { - margin-bottom: var(--spacing-md); - padding: var(--spacing-md); - } - - .workout-header h2 { - margin-bottom: var(--spacing-sm); - } - - .workout-timer { - text-align: center; - } - - .timer-value { - font-size: 1.5rem; - font-weight: bold; - margin-bottom: var(--spacing-xs); - } - - .progress-bar { - height: 8px; - background-color: var(--light-gray); - border-radius: 4px; - overflow: hidden; - } - - .progress { - height: 100%; - background-color: var(--primary-color); - transition: width 0.3s ease; - } - - .current-exercise { - margin-bottom: var(--spacing-md); - padding: var(--spacing-md); - } - - .exercise-name { - margin-bottom: var(--spacing-md); - font-size: 1.2rem; - } - - /* Sets table */ - .sets-table { - width: 100%; - border-collapse: collapse; - margin-bottom: var(--spacing-md); - } - - .sets-table th { - padding: var(--spacing-sm); - text-align: center; - border-bottom: 1px solid var(--border-color); - font-weight: 600; - } - - .sets-table td { - padding: var(--spacing-sm); - text-align: center; - border-bottom: 1px solid var(--border-color); - } - - .sets-table tr.completed { - background-color: rgba(52, 199, 89, 0.1); - } - - .sets-table input { - width: 60px; - padding: 4px; - text-align: center; - } - - .btn-check { - width: 30px; - height: 30px; - border-radius: 50%; - border: 1px solid var(--border-color); - background: white; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - } - - .btn-check.completed { - background-color: var(--success-color); - color: white; - border-color: var(--success-color); - } - - .exercise-notes { - margin-bottom: var(--spacing-md); - } - - .exercise-navigation { - display: flex; - justify-content: flex-end; - } - - /* Pulse animation for the next button */ - .pulse { - animation: pulse 1.5s infinite; - } - - @keyframes pulse { - 0% { - box-shadow: 0 0 0 0 rgba(0, 122, 255, 0.4); - } - 70% { - box-shadow: 0 0 0 10px rgba(0, 122, 255, 0); - } - 100% { - box-shadow: 0 0 0 0 rgba(0, 122, 255, 0); - } - } - - /* Progress list */ - .workout-nav { - margin-bottom: var(--spacing-lg); - } - - .exercises-list ul { - list-style: none; - padding: 0; - margin: 0; - } - - .exercises-list li { - display: flex; - align-items: center; - padding: var(--spacing-sm); - border-bottom: 1px solid var(--border-color); - cursor: pointer; - } - - .exercises-list li.active { - background-color: rgba(0, 122, 255, 0.1); - } - - .exercises-list li.completed { - color: var(--text-muted); - } - - .exercise-number { - width: 24px; - height: 24px; - border-radius: 50%; - background-color: var(--light-gray); - display: flex; - align-items: center; - justify-content: center; - margin-right: var(--spacing-sm); - font-size: 0.8rem; - } - - .exercises-list li.completed .exercise-number { - background-color: var(--success-color); - color: white; - } - - .exercise-list-name { - flex: 1; - } - - .exercise-progress { - font-size: 0.8rem; - color: var(--text-muted); - } - - .workout-actions { - margin-top: var(--spacing-md); - padding: var(--spacing-md); - } - - /* Workout Complete */ - .workout-complete { - text-align: center; - padding: var(--spacing-lg); - } - - .workout-summary h2 { - margin-bottom: var(--spacing-lg); - } - - .summary-stats { - display: flex; - justify-content: center; - gap: var(--spacing-xl); - margin-bottom: var(--spacing-lg); - } - - .stat { - display: flex; - flex-direction: column; - } - - .stat-label { - font-size: 0.9rem; - color: var(--text-muted); - } - - .stat-value { - font-size: 1.5rem; - font-weight: bold; - } - - .feeling-rating { - margin-bottom: var(--spacing-lg); - } - - .stars { - display: flex; - justify-content: center; - gap: var(--spacing-sm); - } - - .star-btn { - background: none; - border: none; - font-size: 1.5rem; - color: #ffb700; - cursor: pointer; - } - - /* Shared */ - .loading { - text-align: center; - padding: var(--spacing-lg); - color: var(--text-muted); - } - - .empty-state { - text-align: center; - padding: var(--spacing-lg); - color: var(--text-muted); - } - - .error-message { - background-color: rgba(255, 59, 48, 0.1); - color: var(--danger-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - - .success-message { - background-color: rgba(52, 199, 89, 0.1); - color: var(--success-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - - .mt-md { - margin-top: var(--spacing-md); - } - - .card { - background-color: white; - border-radius: var(--border-radius); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - padding: var(--spacing-lg); - } - - @media (min-width: 768px) { - .active-workout { - display: grid; - grid-template-columns: 2fr 1fr; - gap: var(--spacing-md); - } - - .workout-header { - grid-column: 1 / -1; - } - - .workout-nav { - grid-column: 2; - grid-row: 2; - } - } - `}</style> - </div> - ); -}; - -export default NewWorkoutPage;
@@ -1,243 +0,0 @@
-import { useState, useEffect } from 'react'; -import { useAppContext } from '../context/AppContext'; -import type { User } from '../types/models'; -import { FaUser, FaSave, FaTimes } from 'react-icons/fa'; - -function parseBirthDate(dateString: string) { - return new Date(dateString).toISOString().split('T')[0]; // Format to YYYY-MM-DD -} - -const ProfilePage = () => { - const { user, updateUser, isLoading, error: contextError } = useAppContext(); - const [formData, setFormData] = useState<User | null>(null); - const [isEditing, setIsEditing] = useState(false); - const [error, setError] = useState<string | null>(null); - const [successMessage, setSuccessMessage] = useState<string | null>(null); - - useEffect(() => { - if (user && !formData) { - setFormData({ ...user }); - } - }, [user, formData]); - - const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => { - const { name, value } = e.target; - - setFormData(prev => { - if (!prev) return prev; - return { ...prev, [name]: value }; - }); - }; - - const handleEditToggle = () => { - if (isEditing) { - // Cancel edit - revert changes - setFormData(user ? { ...user } : null); - } - setIsEditing(!isEditing); - setError(null); - setSuccessMessage(null); - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!formData) return; - - try { - setError(null); - await updateUser(formData); - setSuccessMessage('Profile updated successfully!'); - setIsEditing(false); - - // Clear success message after a few seconds - setTimeout(() => { - setSuccessMessage(null); - }, 3000); - } catch { - setError('Failed to update profile. Please try again.'); - } - }; - - if (isLoading) { - return ( - <div className="page profile-page"> - <h1>Profile</h1> - <div className="loading">Loading profile data...</div> - </div> - ); - } - - if (!formData) { - return ( - <div className="page profile-page"> - <h1>Profile</h1> - <div className="error-message">Could not load profile data.</div> - </div> - ); - } - - return ( - <div className="page profile-page"> - <div className="profile-header"> - <h1>Your Profile</h1> - - <button - onClick={handleEditToggle} - className={`btn ${isEditing ? 'btn-danger' : 'btn-secondary'}`} - > - {isEditing ? ( - <> - <FaTimes /> Cancel - </> - ) : ( - <>Edit Profile</> - )} - </button> - </div> - - {contextError && <div className="error-message">{contextError}</div>} - {error && <div className="error-message">{error}</div>} - {successMessage && <div className="success-message">{successMessage}</div>} - - <div className="card"> - <form onSubmit={handleSubmit}> - <div className="profile-avatar"> - <div className="avatar-circle"> - <FaUser size={40} /> - </div> - </div> - - <div className="form-group"> - <label htmlFor="name">Name</label> - <input - type="text" - id="name" - name="name" - value={formData.name} - onChange={handleInputChange} - disabled={!isEditing} - required - /> - </div> - - <div className="form-group"> - <label htmlFor="isFemale">Gender</label> - <select - id="isFemale" - name="isFemale" - value={formData.isFemale.toString()} - onChange={handleInputChange} - disabled={!isEditing} - required - > - <option value="false">Male</option> - <option value="true">Female</option> - </select> - </div> - - <div className="form-group"> - <label htmlFor="weight">Weight (kg)</label> - <input - type="number" - id="weight" - name="weight" - min="20" - max="300" - step="1" - value={formData.weight ?? 0} - onChange={handleInputChange} - disabled={!isEditing} - required - /> - </div> - - <div className="form-group"> - <label htmlFor="height">Height (cm)</label> - <input - type="number" - id="height" - name="height" - min="0" - max="300" - step="1" - value={formData.height ?? 0} - onChange={handleInputChange} - disabled={!isEditing} - required - /> - </div> - - <div className="form-group"> - <label htmlFor="birthDate">Date of Birth</label> - <input - type="date" - id="birthDate" - name="birthDate" - value={parseBirthDate(formData.birthDate ?? '')} - onChange={handleInputChange} - disabled={!isEditing} - required - /> - </div> - - {isEditing && ( - <div className="form-actions"> - <button type="submit" className="btn btn-primary btn-block"> - <FaSave /> Save Changes - </button> - </div> - )} - </form> - </div> - - <style>{` - .profile-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--spacing-lg); - } - - .profile-avatar { - display: flex; - justify-content: center; - margin-bottom: var(--spacing-xl); - } - - .avatar-circle { - width: 100px; - height: 100px; - border-radius: 50%; - background-color: var(--light-gray); - display: flex; - align-items: center; - justify-content: center; - color: var(--dark-gray); - } - - .form-actions { - margin-top: var(--spacing-lg); - } - - .error-message { - background-color: rgba(255, 59, 48, 0.1); - color: var(--danger-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - - .success-message { - background-color: rgba(52, 199, 89, 0.1); - color: var(--success-color); - padding: var(--spacing-md); - border-radius: var(--border-radius); - margin-bottom: var(--spacing-lg); - } - `}</style> - </div> - ); -}; - -export default ProfilePage;
@@ -1,257 +0,0 @@
-import { useEffect, useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import { FaPlus, FaPlay, FaEdit, FaTrash, FaFilter } from 'react-icons/fa'; -import type { Routine } from '../types/models'; -import { RoutineService } from '../services/api'; - -const WorkoutsPage = () => { - const [routines, setRoutines] = useState<Routine[]>([]); - const [loading, setLoading] = useState<boolean>(true); - const [error, setError] = useState<string | null>(null); - const [searchTerm, setSearchTerm] = useState<string>(''); - const navigate = useNavigate(); - - useEffect(() => { - const fetchRoutines = async () => { - try { - setLoading(true); - const data = await RoutineService.getAll(); - setRoutines(data); - setError(null); - } catch (err) { - console.error('Failed to fetch routines:', err); - setError('Could not load workout routines. Please try again later.'); - } finally { - setLoading(false); - } - }; - - fetchRoutines(); - }, []); - - const handleStartWorkout = (routine: Routine) => { - // Navigate to new workout page with the selected routine - navigate('/new-workout', { state: { routineId: routine.id } }); - }; - - const handleDeleteRoutine = async (id: number) => { - if (window.confirm('Are you sure you want to delete this routine?')) { - try { - await RoutineService.delete(id); - setRoutines(routines.filter(routine => routine.id !== id)); - } catch (err) { - console.error('Failed to delete routine:', err); - alert('Failed to delete routine. Please try again.'); - } - } - }; - - const filteredRoutines = routines.filter(routine => - routine.name.toLowerCase().includes(searchTerm.toLowerCase()) || - routine.description.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - if (loading) { - return ( - <div className="page workouts-page"> - <h1>Workouts</h1> - <div className="loading">Loading routines...</div> - </div> - ); - } - - if (error) { - return ( - <div className="page workouts-page"> - <h1>Workouts</h1> - <div className="error-message">{error}</div> - <div className="mt-lg"> - <Link to="/new-routine" className="btn btn-primary"> - <FaPlus /> Create New Routine - </Link> - </div> - </div> - ); - } - - return ( - <div className="page workouts-page"> - <h1>Workout Routines</h1> - - {/* Search and Filter */} - <div className="search-bar"> - <div className="search-input-container"> - <FaFilter /> - <input - type="text" - placeholder="Search routines..." - value={searchTerm} - onChange={(e) => setSearchTerm(e.target.value)} - className="search-input" - /> - </div> - </div> - - {/* Create New Button */} - <div className="action-buttons mb-lg"> - <Link to="/new-routine" className="btn btn-primary"> - <FaPlus /> Create New Routine - </Link> - </div> - - {/* Routines List */} - {filteredRoutines.length > 0 ? ( - <div className="routines-list"> - {filteredRoutines.map(routine => ( - <div key={routine.id} className="card routine-card"> - <div className="routine-info"> - <h3>{routine.name}</h3> - <p className="routine-description">{routine.description}</p> - <div className="routine-stats"></div> - </div> - - <div className="routine-actions"> - <button - className="btn btn-primary" - onClick={() => handleStartWorkout(routine)} - > - <FaPlay /> Start - </button> - <div className="routine-action-buttons"> - <Link - to={`/new-routine`} - state={{ editRoutine: routine }} - className="btn btn-secondary action-btn" - > - <FaEdit /> - </Link> - <button - className="btn btn-danger action-btn" - onClick={() => routine.id && handleDeleteRoutine(routine.id)} - > - <FaTrash /> - </button> - </div> - </div> - </div> - ))} - </div> - ) : ( - <div className="empty-state"> - {searchTerm ? ( - <p>No routines found matching "{searchTerm}"</p> - ) : ( - <> - <p>You haven't created any workout routines yet.</p> - <p className="mt-sm">Create your first routine to get started!</p> - <Link to="/new-routine" className="btn btn-primary mt-md"> - <FaPlus /> Create Routine - </Link> - </> - )} - </div> - )} - - <style>{` - .search-bar { - margin-bottom: var(--spacing-md); - } - - .search-input-container { - display: flex; - align-items: center; - background-color: white; - border-radius: var(--border-radius); - padding: 0 var(--spacing-md); - border: 1px solid var(--light-gray); - } - - .search-input { - border: none; - padding: var(--spacing-sm) var(--spacing-sm); - flex: 1; - } - - .search-input:focus { - outline: none; - } - - .action-buttons { - display: flex; - justify-content: flex-end; - margin: var(--spacing-md) 0; - } - - .routines-list { - display: grid; - gap: var(--spacing-md); - } - - .routine-card { - display: flex; - flex-direction: column; - } - - .routine-info { - flex: 1; - margin-bottom: var(--spacing-md); - } - - .routine-description { - color: var(--dark-gray); - margin: var(--spacing-sm) 0; - } - - .routine-stats { - display: flex; - gap: var(--spacing-md); - color: var(--dark-gray); - font-size: 0.9rem; - } - - .routine-actions { - display: flex; - justify-content: space-between; - align-items: center; - } - - .routine-action-buttons { - display: flex; - gap: var(--spacing-sm); - } - - .action-btn { - padding: 8px; - min-width: 40px; - } - - .empty-state { - text-align: center; - padding: var(--spacing-xl) var(--spacing-md); - background-color: white; - border-radius: var(--border-radius); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - } - - @media (min-width: 768px) { - .routine-card { - flex-direction: row; - } - - .routine-info { - margin-bottom: 0; - margin-right: var(--spacing-lg); - } - - .routine-actions { - flex-direction: column; - align-items: flex-end; - justify-content: space-between; - } - } - `}</style> - </div> - ); -}; - -export default WorkoutsPage;
@@ -1,142 +0,0 @@
-import type { User, Exercise, Muscle, Routine, RecordRoutine, WorkoutStats } from '../types/models'; - -const API_BASE = '/api'; - -class BaseService<T> { - protected endpoint: string; - constructor(endpoint: string) { - this.endpoint = endpoint; - } - - protected async request<R>(path: string, options?: RequestInit): Promise<R> { - const response = await fetch(`${API_BASE}${path}`, { - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - ...options, - }); - - if (!response.ok) { - throw new Error(`API Error: ${response.status} ${response.statusText}`); - } - - return response.json(); - } - - async getAll(): Promise<T[]> { - return this.request<T[]>(this.endpoint); - } - - async get(id: number): Promise<T> { - return this.request<T>(`${this.endpoint}/${id}`); - } - - async create(data: Omit<T, 'id' | 'createdAt' | 'updatedAt'>): Promise<T> { - return this.request<T>(this.endpoint, { - method: 'POST', - body: JSON.stringify(data), - }); - } - - async update(id: number, data: Partial<T>): Promise<T> { - return this.request<T>(`${this.endpoint}/${id}`, { - method: 'PUT', - body: JSON.stringify(data), - }); - } - - async delete(id: number): Promise<void> { - await this.request<void>(`${this.endpoint}/${id}`, { - method: 'DELETE', - }); - } -} - -class UserService extends BaseService<User> { - constructor() { - super('/users'); - } - - // Override get to default to user ID 1 - async get(id: number = 1): Promise<User> { - return super.get(id); - } -} - -class ExerciseService extends BaseService<Exercise> { - constructor() { - super('/exercises'); - } -} - -class MuscleService extends BaseService<Muscle> { - constructor() { - super('/muscles'); - } -} - -class RoutineService extends BaseService<Routine> { - constructor() { - super('/routines'); - } -} - -class RecordService extends BaseService<RecordRoutine> { - constructor() { - super('/records'); - } -} - -class StatsService { - protected async request<R>(path: string, options?: RequestInit): Promise<R> { - const response = await fetch(`${API_BASE}${path}`, { - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - ...options, - }); - - if (!response.ok) { - throw new Error(`API Error: ${response.status} ${response.statusText}`); - } - - return response.json(); - } - - async get(): Promise<WorkoutStats> { - return this.request<WorkoutStats>('/stats'); - } -} - -class HealthService { - protected async request<R>(path: string, options?: RequestInit): Promise<R> { - const response = await fetch(`${API_BASE}${path}`, { - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - ...options, - }); - - if (!response.ok) { - throw new Error(`API Error: ${response.status} ${response.statusText}`); - } - - return response.json(); - } - - async ping(): Promise<{ message: string }> { - return this.request<{ message: string }>('/ping'); - } -} - -// Export service instances -export const userService = new UserService(); -export const exerciseService = new ExerciseService(); -export const muscleService = new MuscleService(); -export const routineService = new RoutineService(); -export const recordService = new RecordService(); -export const statsService = new StatsService(); -export const healthService = new HealthService();
@@ -1,137 +0,0 @@
-export interface User { - id?: number; - name: string; - isFemale: boolean; - height?: number; // In cm - weight?: number; // In kg - birthDate?: string; - createdAt?: string; - updatedAt?: string; -} - -export interface Muscle { - id: number; - name: string; - createdAt: string; - updatedAt: string; -} - -export interface Exercise { - id: number; - name: string; - level: string; - category: string; - force?: string; - mechanic?: string; - equipment?: string; - instructions?: string; - primaryMuscles: Muscle[]; - secondaryMuscles: Muscle[]; - createdAt: string; - updatedAt: string; -} - -export interface Set { - id: number; - exerciseItemId: number; - reps: number; - weight: number; - duration: number; // In seconds - orderIndex: number; - createdAt: string; - updatedAt: string; -} - -export interface ExerciseItem { - id: number; - routineItemId: number; - exerciseId: number; - orderIndex: number; - createdAt: string; - updatedAt: string; - exercise: Exercise; - sets: Set[]; -} - -export interface RoutineItem { - id: number; - routineId: number; - type: string; // "exercise" or "superset" - restTime: number; // In seconds - orderIndex: number; - createdAt: string; - updatedAt: string; - exerciseItems: ExerciseItem[]; -} - -export interface Routine { - id: number; - name: string; - description: string; - createdAt: string; - updatedAt: string; - items: RoutineItem[]; -} - -export interface RecordSet { - id: number; - recordExerciseItemId: number; - setId: number; - actualReps: number; - actualWeight: number; - actualDuration: number; // In seconds - completedAt: string; - orderIndex: number; - createdAt: string; - updatedAt: string; - set: Set; -} - -export interface RecordExerciseItem { - id: number; - recordItemId: number; - exerciseItemId: number; - orderIndex: number; - createdAt: string; - updatedAt: string; - exerciseItem: ExerciseItem; - recordSets: RecordSet[]; -} - -export interface RecordItem { - id: number; - recordRoutineId: number; - routineItemId: number; - duration?: number; // In seconds - actualRestTime?: number; // In seconds - orderIndex: number; - createdAt: string; - updatedAt: string; - routineItem: RoutineItem; - recordExerciseItems: RecordExerciseItem[]; -} - -export interface RecordRoutine { - id: number; - routineId: number; - duration?: number; // In seconds - createdAt: string; - updatedAt: string; - routine: Routine; - recordItems: RecordItem[]; -} - -export interface WorkoutStats { - totalWorkouts: number; - totalMinutes: number; - totalExercises: number; - mostFrequentExercise?: { - name: string; - count: number; - }; - mostFrequentRoutine?: { - name: string; - count: number; - }; - recentWorkouts: RecordRoutine[]; -}
@@ -1,27 +0,0 @@
-{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src"] -}
@@ -1,7 +0,0 @@
-{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -}
@@ -1,25 +0,0 @@
-{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] -}
@@ -1,16 +0,0 @@
-import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], - server: { - proxy: { - '/api': { - target: 'http://localhost:3000', - changeOrigin: true, - secure: false, - }, - }, - }, -})