diff --git a/backend/internal/handlers/auth/auth.go b/backend/internal/handlers/auth/auth.go index a4a3019f..10bfe430 100644 --- a/backend/internal/handlers/auth/auth.go +++ b/backend/internal/handlers/auth/auth.go @@ -142,12 +142,20 @@ func (service *AuthService) GetCurrentUser() fiber.Handler { return errs.AuthenticationError() } - user, err := client.GetCurrentUser(c.Context()) + githubUser, err := client.GetCurrentUser(c.Context()) if err != nil { return errs.AuthenticationError() } - return c.Status(fiber.StatusOK).JSON(fiber.Map{"user": user}) + user, err := service.store.GetUserByGitHubID(c.Context(), githubUser.ID) + if err != nil { + return errs.InternalServerError() + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "github_user": githubUser, + "user": user, + }) } } diff --git a/backend/internal/handlers/classrooms/assignments/assignments.go b/backend/internal/handlers/classrooms/assignments/assignments.go index 852b5228..545276dc 100644 --- a/backend/internal/handlers/classrooms/assignments/assignments.go +++ b/backend/internal/handlers/classrooms/assignments/assignments.go @@ -142,6 +142,14 @@ func (s *AssignmentService) generateAssignmentToken() fiber.Handler { return errs.BadRequest(err) } + // if the link is permenant, use the existing permanent token + if body.Duration == nil { + assignmentToken, err := s.store.GetPermanentAssignmentTokenByAssignmentID(c.Context(), assignmentID) + if err == nil { + return c.Status(http.StatusOK).JSON(fiber.Map{"token": assignmentToken.Token}) + } + } + token, err := utils.GenerateToken(16) if err != nil { return errs.InternalServerError() diff --git a/backend/internal/handlers/classrooms/classrooms.go b/backend/internal/handlers/classrooms/classrooms.go index dec3cfac..b0e97679 100644 --- a/backend/internal/handlers/classrooms/classrooms.go +++ b/backend/internal/handlers/classrooms/classrooms.go @@ -192,7 +192,6 @@ func (s *ClassroomService) getClassroomUsers() fiber.Handler { usersInClassroom, err := s.store.GetUsersInClassroom(c.Context(), classroomID) if err != nil { - return errs.InternalServerError() } @@ -213,6 +212,7 @@ func (s *ClassroomService) getClassroomUsers() fiber.Handler { return c.Status(http.StatusOK).JSON(fiber.Map{"users": updatedUsersInClassroom}) } } + func (s *ClassroomService) getRubricsInClassroom() fiber.Handler { return func(c *fiber.Ctx) error { classroomID, err := strconv.ParseInt(c.Params("classroom_id"), 10, 64) @@ -321,6 +321,14 @@ func (s *ClassroomService) generateClassroomToken() fiber.Handler { return err } + // if the link is permenant, use the existing permanent token + if body.Duration == nil { + classroomToken, err := s.store.GetPermanentClassroomTokenByClassroomIDAndRole(c.Context(), classroomID, classroomRole) + if err == nil { + return c.Status(http.StatusOK).JSON(fiber.Map{"token": classroomToken.Token}) + } + } + token, err := utils.GenerateToken(16) if err != nil { return errs.InternalServerError() diff --git a/backend/internal/handlers/users/users.go b/backend/internal/handlers/users/users.go index ee236d3c..bdea2d42 100644 --- a/backend/internal/handlers/users/users.go +++ b/backend/internal/handlers/users/users.go @@ -20,13 +20,19 @@ func (s *UserService) GetUser() fiber.Handler { return errs.BadRequest(err) } - user, err := client.GetUser(c.Context(), userName) + githubUser, err := client.GetUser(c.Context(), userName) + if err != nil { + return errs.AuthenticationError() + } + + user, err := s.store.GetUserByGitHubID(c.Context(), *githubUser.ID) if err != nil { return errs.AuthenticationError() } return c.Status(http.StatusOK).JSON(fiber.Map{ - "user": user, + "user": user, + "github_user": githubUser, }) } } diff --git a/backend/internal/storage/postgres/assignment_outlines.go b/backend/internal/storage/postgres/assignment_outlines.go index ba49c90a..bd4bd106 100644 --- a/backend/internal/storage/postgres/assignment_outlines.go +++ b/backend/internal/storage/postgres/assignment_outlines.go @@ -58,6 +58,26 @@ func (db *DB) GetAssignmentByToken(ctx context.Context, token string) (models.As return assignmentOutline, nil } +func (db *DB) GetPermanentAssignmentTokenByAssignmentID(ctx context.Context, assignmentID int64) (models.AssignmentToken, error) { + var tokenData models.AssignmentToken + err := db.connPool.QueryRow(ctx, ` + SELECT assignment_outline_id, token, created_at, expires_at + FROM assignment_outline_tokens + WHERE assignment_outline_id = $1 AND expires_at IS NULL + `, assignmentID).Scan( + &tokenData.AssignmentID, + &tokenData.Token, + &tokenData.CreatedAt, + &tokenData.ExpiresAt, + ) + + if err != nil { + return models.AssignmentToken{}, errs.NewDBError(err) + } + + return tokenData, nil +} + func (db *DB) GetAssignmentsInClassroom(ctx context.Context, classroomID int64) ([]models.AssignmentOutline, error) { rows, err := db.connPool.Query(ctx, "SELECT * FROM assignment_outlines WHERE classroom_id = $1", classroomID) if err != nil { diff --git a/backend/internal/storage/postgres/assignment_templates.go b/backend/internal/storage/postgres/assignment_templates.go index c981a7d6..c9329ab7 100644 --- a/backend/internal/storage/postgres/assignment_templates.go +++ b/backend/internal/storage/postgres/assignment_templates.go @@ -53,7 +53,7 @@ func (db *DB) GetAssignmentTemplateByID(ctx context.Context, templateID int64) ( func (db *DB) GetAssignmentTemplateByAssignmentID(ctx context.Context, assignmentID int64) (models.AssignmentTemplate, error) { var assignmentTemplate models.AssignmentTemplate - err := db.connPool.QueryRow(ctx, "SELECT * FROM assignment_templates JOIN assignment_outlines ON assignment_templates.template_repo_id = assignment_outlines.template_id WHERE assignment_outlines.id = $1", assignmentID).Scan( + err := db.connPool.QueryRow(ctx, "SELECT assignment_templates.* FROM assignment_templates JOIN assignment_outlines ON assignment_templates.template_repo_id = assignment_outlines.template_id WHERE assignment_outlines.id = $1", assignmentID).Scan( &assignmentTemplate.TemplateID, &assignmentTemplate.TemplateRepoOwner, &assignmentTemplate.TemplateRepoName, diff --git a/backend/internal/storage/postgres/classrooms.go b/backend/internal/storage/postgres/classrooms.go index a53e0592..22ef59e3 100644 --- a/backend/internal/storage/postgres/classrooms.go +++ b/backend/internal/storage/postgres/classrooms.go @@ -302,6 +302,27 @@ func (db *DB) GetClassroomToken(ctx context.Context, token string) (models.Class return tokenData, nil } +func (db *DB) GetPermanentClassroomTokenByClassroomIDAndRole(ctx context.Context, classroomID int64, classroomRole models.ClassroomRole) (models.ClassroomToken, error) { + var tokenData models.ClassroomToken + err := db.connPool.QueryRow(ctx, ` + SELECT classroom_id, classroom_role, token, created_at, expires_at + FROM classroom_tokens + WHERE classroom_id = $1 AND classroom_role = $2 AND expires_at IS NULL + `, classroomID, classroomRole).Scan( + &tokenData.ClassroomID, + &tokenData.ClassroomRole, + &tokenData.Token, + &tokenData.CreatedAt, + &tokenData.ExpiresAt, + ) + + if err != nil { + return models.ClassroomToken{}, errs.NewDBError(err) + } + + return tokenData, nil +} + func (db *DB) GetNumberOfStudentsInClassroom(ctx context.Context, classroomID int64) (int, error) { var count int err := db.connPool.QueryRow(ctx, ` diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 21b2c804..e1d6b906 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -59,6 +59,7 @@ type Classroom interface { GetUserClassroomsInOrg(ctx context.Context, orgID int64, userID int64) ([]models.ClassroomUser, error) CreateClassroomToken(ctx context.Context, tokenData models.ClassroomToken) (models.ClassroomToken, error) GetClassroomToken(ctx context.Context, token string) (models.ClassroomToken, error) + GetPermanentClassroomTokenByClassroomIDAndRole(ctx context.Context, classroomID int64, classroomRole models.ClassroomRole) (models.ClassroomToken, error) GetNumberOfStudentsInClassroom(ctx context.Context, classroomID int64) (int, error) } @@ -80,6 +81,7 @@ type AssignmentOutline interface { GetTotalWorkCommits(ctx context.Context, assignmentID int) (int, error) GetAssignmentByToken(ctx context.Context, token string) (models.AssignmentOutline, error) CreateAssignmentToken(ctx context.Context, tokenData models.AssignmentToken) (models.AssignmentToken, error) + GetPermanentAssignmentTokenByAssignmentID(ctx context.Context, assignmentID int64) (models.AssignmentToken, error) } type AssignmentTemplate interface { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1dee167c..7c5f7b5c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,9 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@tanstack/query-sync-storage-persister": "^5.62.7", + "@tanstack/react-query": "^5.0.0", + "@tanstack/react-query-persist-client": "^5.62.7", "@types/d3": "^7.4.3", "@types/react-router-dom": "^5.3.3", "chart.js": "^4.4.7", @@ -15,9 +18,9 @@ "d3": "^7.9.0", "js-cookie": "^3.0.5", "prismjs": "^1.29.0", - "react": "^18.3.1", + "react": "^18.0.0", "react-chartjs-2": "^5.2.0", - "react-dom": "^18.3.1", + "react-dom": "^18.0.0", "react-icons": "^5.3.0", "react-resizable": "^3.0.5", "react-router-dom": "^6.26.2", @@ -66,12 +69,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz", - "integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -79,30 +83,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.9.tgz", - "integrity": "sha512-yD+hEuJ/+wAJ4Ox2/rpNv5HIuPG82x3ZlQvYVn8iYCprdxzE7P1udpGF1jyjQVBU4dgznN+k2h103vxZ7NdPyw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.9.tgz", - "integrity": "sha512-WYvQviPw+Qyib0v92AwNIrdLISTp7RfDkM7bPqBvpbnhY4wq8HvHBZREVdYDXk98C8BkOIVnHAY3yvj7AVISxQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helpers": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", "@babel/template": "^7.25.9", "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -127,12 +131,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.9.tgz", - "integrity": "sha512-omlUGkr5EaoIJrhLf9CJ0TvjBRpd9+AXRG//0GEQ9THSo8wPiTlbpy1/Ow8ZTrbXpjd9FHXfbFQx32I04ht0FA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dev": true, "dependencies": { - "@babel/types": "^7.25.9", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -180,13 +185,12 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.9.tgz", - "integrity": "sha512-TvLZY/F3+GvdRYFZFyxMvnsKi+4oJdgZzU3BoGN9Uc2d9C6zfNwJcKKhjqLAhK8i46mv93jsO74fDh3ih6rpHA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, @@ -206,19 +210,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", @@ -247,40 +238,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.9.tgz", - "integrity": "sha512-oKWp3+usOJSzDZOucZUAMayhPz/xVjzymyDzUN8dk0Wd3RWMlGLXi07UCQ/CgQVb8LvXx3XBajJH4XGgkt7H7g==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, "dependencies": { "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", - "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", - "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dev": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -334,16 +310,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -361,9 +337,9 @@ } }, "node_modules/@babel/types": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", - "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -742,36 +718,39 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "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/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -802,18 +781,21 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -868,27 +850,27 @@ } }, "node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -899,27 +881,40 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", - "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", - "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "dependencies": { - "@humanfs/core": "^0.19.0", + "@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, + "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", @@ -934,9 +929,9 @@ } }, "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==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, "engines": { "node": ">=18.18" @@ -947,9 +942,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -1048,9 +1043,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", - "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", "engines": { "node": ">=14.0.0" } @@ -1090,9 +1085,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -1103,9 +1098,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -1116,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -1129,9 +1124,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -1141,10 +1136,36 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -1155,9 +1176,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -1168,9 +1189,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -1181,9 +1202,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -1193,10 +1214,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -1207,9 +1241,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -1220,9 +1254,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -1233,9 +1267,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -1246,9 +1280,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -1259,9 +1293,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -1272,9 +1306,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -1285,9 +1319,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -1297,6 +1331,71 @@ "win32" ] }, + "node_modules/@tanstack/query-core": { + "version": "5.62.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz", + "integrity": "sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-persist-client-core": { + "version": "5.62.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.62.7.tgz", + "integrity": "sha512-9HcaD9rEp2nGWnrw2osK5UCSKJbJKEdn+MEhVVfnUPSFN7MZFpFZxpRCHJi3fRpWOYsVeH9EFODX+aoJaniJMA==", + "dependencies": { + "@tanstack/query-core": "5.62.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister": { + "version": "5.62.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.62.7.tgz", + "integrity": "sha512-fcXCT65VgmgKsSM5XfRlk0hXnQMpkHLCMpOjqq7Rl2DMjHZcKjtgSEw+drHZBggJ5dqAhPd5DAcZ/utIE192nw==", + "dependencies": { + "@tanstack/query-core": "5.62.7", + "@tanstack/query-persist-client-core": "5.62.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.62.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz", + "integrity": "sha512-+xCtP4UAFDTlRTYyEjLx0sRtWyr5GIk7TZjZwBu4YaNahi3Rt2oMyRqfpfVrtwsqY2sayP4iXVCwmC+ZqqFmuw==", + "dependencies": { + "@tanstack/query-core": "5.62.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-persist-client": { + "version": "5.62.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.62.7.tgz", + "integrity": "sha512-RmEJ3YvsK7lv1Of3CCXBgHDtoZVwHMtTKCTegZz+xijVJsgJaNNfel4YTpbQ0ydnWT2IcohdqnHUtBE6p1KCIA==", + "dependencies": { + "@tanstack/query-persist-client-core": "5.62.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.62.7", + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1511,9 +1610,9 @@ } }, "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", - "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==" }, "node_modules/@types/d3-selection": { "version": "3.0.11", @@ -1529,9 +1628,9 @@ } }, "node_modules/@types/d3-time": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", - "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" }, "node_modules/@types/d3-time-format": { "version": "4.0.3", @@ -1567,9 +1666,9 @@ "dev": true }, "node_modules/@types/geojson": { - "version": "7946.0.14", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + "version": "7946.0.15", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz", + "integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==" }, "node_modules/@types/history": { "version": "4.7.11", @@ -1588,28 +1687,13 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, - "node_modules/@types/lodash": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", - "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/node": { - "version": "22.7.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", - "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/prismjs": { @@ -1619,26 +1703,26 @@ "dev": true }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "18.3.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz", + "integrity": "sha512-oh8AMIC4Y2ciKufU8hnKgs+ufgbA/dhPTACaZPM86AbwX9QwnFtSoPWEeRUj8fge+v6kFt78BXcDhAU1SrrAsw==", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", "dev": true, - "dependencies": { - "@types/react": "*" + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/react-resizable": { @@ -1670,16 +1754,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", - "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz", + "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/type-utils": "8.11.0", - "@typescript-eslint/utils": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/type-utils": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1694,24 +1778,20 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", - "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz", + "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "debug": "^4.3.4" }, "engines": { @@ -1722,22 +1802,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", - "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz", + "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0" + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1748,13 +1824,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz", - "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz", + "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/utils": "8.11.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/utils": "8.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1765,16 +1841,15 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", - "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz", + "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1785,13 +1860,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", - "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz", + "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1806,22 +1881,20 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", - "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz", + "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0" + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1831,17 +1904,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", - "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz", + "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.11.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.18.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1851,15 +1925,27 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/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, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", - "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", "dev": true, "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/core": "^7.26.0", + "@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.14.2" }, @@ -1867,13 +1953,13 @@ "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1908,15 +1994,18 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/argparse": { @@ -2167,16 +2256,44 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", + "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "get-intrinsic": "^1.2.5" }, "engines": { "node": ">= 0.4" @@ -2195,9 +2312,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001669", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", - "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", "dev": true, "funding": [ { @@ -2215,17 +2332,19 @@ ] }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chart.js": { @@ -2258,18 +2377,21 @@ } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "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, "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "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 }, "node_modules/commander": { @@ -2746,9 +2868,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -2822,16 +2944,30 @@ "node": ">=0.10.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.45", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.45.tgz", - "integrity": "sha512-vOzZS6uZwhhbkZbcRyiy99Wg+pYFV5hk+5YaECvx0+Z31NR3Tt5zS6dze2OepT6PCTzVzT0dIJItti+uAW5zmw==", + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", "dev": true }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.23.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", + "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -2849,7 +2985,7 @@ "function.prototype.name": "^1.1.6", "get-intrinsic": "^1.2.4", "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", + "globalthis": "^1.0.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", @@ -2865,10 +3001,10 @@ "is-string": "^1.0.7", "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", + "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.9", @@ -2889,13 +3025,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -2910,9 +3043,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz", - "integrity": "sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", "dev": true, "dependencies": { "call-bind": "^1.0.7", @@ -2923,6 +3056,7 @@ "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "globalthis": "^1.0.4", + "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", @@ -2970,14 +3104,14 @@ } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -3034,40 +3168,43 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", - "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.13.0", - "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.16.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.1", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.5", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", + "eslint-scope": "^8.2.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", @@ -3081,8 +3218,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -3177,9 +3313,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.1.0-rc-fb9a90fa48-20240614", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz", - "integrity": "sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", + "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", "dev": true, "engines": { "node": ">=10" @@ -3189,12 +3325,12 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz", - "integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz", + "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==", "dev": true, "peerDependencies": { - "eslint": ">=7" + "eslint": ">=8.40" } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { @@ -3229,9 +3365,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -3256,21 +3392,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3281,56 +3402,10 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/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, - "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/eslint/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, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/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 - }, - "node_modules/eslint/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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "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, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3339,15 +3414,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3360,27 +3426,15 @@ "node": "*" } }, - "node_modules/eslint/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, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3390,9 +3444,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "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, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3564,9 +3618,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true }, "node_modules/for-each": { @@ -3651,16 +3705,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3699,9 +3758,9 @@ } }, "node_modules/globals": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", - "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", + "version": "15.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", + "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", "dev": true, "engines": { "node": ">=18" @@ -3727,12 +3786,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3754,12 +3813,12 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -3775,10 +3834,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -3787,9 +3849,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "engines": { "node": ">= 0.4" @@ -3924,25 +3986,28 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3979,11 +4044,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -3994,12 +4061,13 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4018,12 +4086,15 @@ } }, "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4090,12 +4161,13 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.0.tgz", + "integrity": "sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4105,13 +4177,15 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4148,12 +4222,13 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.0.tgz", + "integrity": "sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bind": "^1.0.7", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4163,12 +4238,14 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4245,16 +4322,17 @@ "dev": true }, "node_modules/iterator.prototype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", - "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.4.tgz", + "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", "dev": true, "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "reflect.getprototypeof": "^1.0.8", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -4286,9 +4364,9 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "bin": { "jsesc": "bin/jsesc" @@ -4382,14 +4460,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -4417,6 +4488,15 @@ "yallist": "^3.0.2" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4461,9 +4541,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -4485,9 +4565,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "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 }, "node_modules/normalize-range": { @@ -4508,9 +4588,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, "engines": { "node": ">= 0.4" @@ -4706,9 +4786,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -4726,7 +4806,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -4787,9 +4867,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -4893,23 +4973,10 @@ "react": "^18.3.1" } }, - "node_modules/react-draggable": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", - "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", - "dependencies": { - "clsx": "^1.1.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" - } - }, "node_modules/react-icons": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", - "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", + "integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==", "peerDependencies": { "react": "*" } @@ -4940,12 +5007,25 @@ "react": ">= 16.3" } }, + "node_modules/react-resizable/node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", "dependencies": { - "@remix-run/router": "1.20.0" + "@remix-run/router": "1.21.0" }, "engines": { "node": ">=14.0.0" @@ -4955,12 +5035,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" }, "engines": { "node": ">=14.0.0" @@ -4980,18 +5060,19 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", + "dunder-proto": "^1.0.0", + "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" }, "engines": { "node": ">= 0.4" @@ -5060,9 +5141,9 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "dev": true, "dependencies": { "@types/estree": "1.0.6" @@ -5075,22 +5156,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -5123,14 +5207,15 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -5141,14 +5226,14 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -5236,15 +5321,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5254,23 +5393,19 @@ } }, "node_modules/simplebar-core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/simplebar-core/-/simplebar-core-1.2.6.tgz", - "integrity": "sha512-H5NYU+O+uvqOH5VXw3+lgoc1vTI6jL8LOZJsw4xgRpV7uIPjRpmLPdz0TrouxwKHBhpVLzVIlyKhaRLelIThMw==", - "license": "MIT", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simplebar-core/-/simplebar-core-1.3.0.tgz", + "integrity": "sha512-LpWl3w0caz0bl322E68qsrRPpIn+rWBGAaEJ0lUJA7Xpr2sw92AkIhg6VWj988IefLXYh50ILatfAnbNoCFrlA==", "dependencies": { - "@types/lodash-es": "^4.17.6", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21" + "lodash": "^4.17.21" } }, "node_modules/simplebar-react": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/simplebar-react/-/simplebar-react-3.2.6.tgz", - "integrity": "sha512-8jDiBuVCG86JmOrsmkA+4q77iFAEbhU9EX62PohLisg3dnxdLXFFhkxnx2Es3Cxt8IlZFlJsF9GaobFL3ukwiA==", - "license": "MIT", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/simplebar-react/-/simplebar-react-3.3.0.tgz", + "integrity": "sha512-sxzy+xRuU41He4tT4QLGYutchtOuye/xxVeq7xhyOiwMiHNK1ZpvbOTyy+7P0i7gfpXLGTJ8Bep8+4Mhdgtz/g==", "dependencies": { - "simplebar-core": "^1.2.6" + "simplebar-core": "^1.3.0" }, "peerDependencies": { "react": ">=16.8.0" @@ -5322,15 +5457,18 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5340,15 +5478,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5383,15 +5525,15 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "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, "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -5422,12 +5564,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5441,9 +5577,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "engines": { "node": ">=16" @@ -5453,9 +5589,9 @@ } }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "node_modules/type-check": { @@ -5504,9 +5640,9 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", @@ -5514,7 +5650,8 @@ "for-each": "^0.3.3", "gopd": "^1.0.1", "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -5524,17 +5661,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -5544,9 +5681,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5557,14 +5694,14 @@ } }, "node_modules/typescript-eslint": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.11.0.tgz", - "integrity": "sha512-cBRGnW3FSlxaYwU8KfAewxFK5uzeOAp0l2KebIlPDOT5olVi65KDG/yjBooPBG0kGW/HLkoz1c/iuBFehcS3IA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.0.tgz", + "integrity": "sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.11.0", - "@typescript-eslint/parser": "8.11.0", - "@typescript-eslint/utils": "8.11.0" + "@typescript-eslint/eslint-plugin": "8.18.0", + "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/utils": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5573,10 +5710,9 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/unbox-primitive": { @@ -5595,9 +5731,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, "node_modules/update-browserslist-db": { @@ -5646,9 +5782,9 @@ "dev": true }, "node_modules/vite": { - "version": "5.4.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", - "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "dependencies": { "esbuild": "^0.21.3", @@ -5705,22 +5841,25 @@ } }, "node_modules/vite-plugin-eslint2": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/vite-plugin-eslint2/-/vite-plugin-eslint2-5.0.1.tgz", - "integrity": "sha512-Pr/Df4mpYtKoYOFMRQonoDE/SH56ah2cbtjyQmRveh+Wz2pgk/RfTM3ApZ6xrAEpmIyBctfy5JoBmcWmikKTlg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vite-plugin-eslint2/-/vite-plugin-eslint2-5.0.3.tgz", + "integrity": "sha512-kbjjbSyxSYK1oK0kOnSVs2er8DhqNbVA5pNN21SJo8AldQIOgG4LVQvwp6ISYMDXQaaBMOCrmXFTfGkQUjIZ1g==", "dev": true, "dependencies": { - "@rollup/pluginutils": "^5.1.2", + "@rollup/pluginutils": "^5.1.3", "debug": "^4.3.7" }, "engines": { "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/modyqyw" + }, "peerDependencies": { - "@types/eslint": "^7.0.0 || ^8.0.0 || ^9.0.0-0", - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0-0", + "@types/eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", "rollup": "^2.0.0 || ^3.0.0 || ^4.0.0", - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "@types/eslint": { @@ -5747,39 +5886,43 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz", + "integrity": "sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==", "dev": true, "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.0", + "is-number-object": "^1.1.0", + "is-string": "^1.1.0", + "is-symbol": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-builtin-type": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", - "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, "dependencies": { + "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", + "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -5807,9 +5950,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", diff --git a/frontend/package.json b/frontend/package.json index 2bdc29e5..e1620c11 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,9 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/query-sync-storage-persister": "^5.62.7", + "@tanstack/react-query": "^5.0.0", + "@tanstack/react-query-persist-client": "^5.62.7", "@types/d3": "^7.4.3", "@types/react-router-dom": "^5.3.3", "chart.js": "^4.4.7", @@ -18,9 +21,9 @@ "d3": "^7.9.0", "js-cookie": "^3.0.5", "prismjs": "^1.29.0", - "react": "^18.3.1", + "react": "^18.0.0", "react-chartjs-2": "^5.2.0", - "react-dom": "^18.3.1", + "react-dom": "^18.0.0", "react-icons": "^5.3.0", "react-resizable": "^3.0.5", "react-router-dom": "^6.26.2", diff --git a/frontend/src/api/users.ts b/frontend/src/api/users.ts index c87db25f..ab2893dc 100644 --- a/frontend/src/api/users.ts +++ b/frontend/src/api/users.ts @@ -1,6 +1,6 @@ const base_url: string = import.meta.env.VITE_PUBLIC_API_DOMAIN as string; -export const fetchCurrentUser = async (): Promise => { +export const fetchCurrentUser = async (): Promise => { const response = await fetch(`${base_url}/user`, { method: "GET", credentials: "include", @@ -17,13 +17,13 @@ export const fetchCurrentUser = async (): Promise => { } } - const data: IGitHubUserResponse = await response.json(); - return data.user; + const data: IUserResponse = await response.json(); + return data; }; export const fetchUser = async ( user_name: string -): Promise => { +): Promise => { const response = await fetch(`${base_url}/users/user/${user_name}`, { method: "GET", credentials: "include", diff --git a/frontend/src/components/Button/index.tsx b/frontend/src/components/Button/index.tsx index 9fd14f09..3db26a00 100644 --- a/frontend/src/components/Button/index.tsx +++ b/frontend/src/components/Button/index.tsx @@ -2,18 +2,20 @@ import { Link } from "react-router-dom"; import "./styles.css"; interface IButtonProps extends React.ButtonHTMLAttributes { href?: string; - variant?: "primary" | "secondary" | "warning-primary" | "warning-secondary"; + variant?: "primary" | "secondary" | "warning-primary" | "warning-secondary" | "disabled"; size?: "default" | "small"; newTab?: boolean; state?: object; + disabled?: boolean; } const ButtonWrapper: React.FC = ({ children, href, newTab = false, - state + state, + disabled = false, }) => { - return href ? ( + return (href && !disabled) ? ( {children} @@ -29,10 +31,11 @@ const Button: React.FC = ({ size = "default", newTab, state, + disabled, ...props }) => { return ( - + diff --git a/frontend/src/components/LoadingSpinner/index.tsx b/frontend/src/components/LoadingSpinner/index.tsx index bc29ce6a..63921abb 100644 --- a/frontend/src/components/LoadingSpinner/index.tsx +++ b/frontend/src/components/LoadingSpinner/index.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { ClipLoader } from "react-spinners"; interface LoadingSpinnerProps { diff --git a/frontend/src/components/LoadingSpinner/styles.css b/frontend/src/components/LoadingSpinner/styles.css new file mode 100644 index 00000000..7ee4f1a9 --- /dev/null +++ b/frontend/src/components/LoadingSpinner/styles.css @@ -0,0 +1,6 @@ +.LoadingSpinner { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} \ No newline at end of file diff --git a/frontend/src/components/MultiStepForm/index.tsx b/frontend/src/components/MultiStepForm/index.tsx index 35060475..6a9ab695 100644 --- a/frontend/src/components/MultiStepForm/index.tsx +++ b/frontend/src/components/MultiStepForm/index.tsx @@ -1,8 +1,8 @@ import { useState, useCallback, useEffect, ReactElement } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import ClipLoader from "react-spinners/ClipLoader"; import Button from "../Button"; import './styles.css'; +import LoadingSpinner from "../LoadingSpinner"; const MultiStepForm = ({ steps, @@ -123,7 +123,7 @@ const MultiStepForm = ({ {/* Spinner Overlay */} {isLoading && (
- +
)} diff --git a/frontend/src/components/UserGroupCard/index.tsx b/frontend/src/components/UserGroupCard/index.tsx index c6a3e952..012b17a5 100644 --- a/frontend/src/components/UserGroupCard/index.tsx +++ b/frontend/src/components/UserGroupCard/index.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from "react"; +import { useQueries } from '@tanstack/react-query'; +import React from "react"; import "./styles.css"; import { fetchUser } from "@/api/users"; @@ -13,41 +14,29 @@ const UserGroupCard: React.FC = ({ givenUsersList, onClick, }) => { - const [userMap, setUserMap] = useState>( - new Map() - ); - useEffect(() => { - const loadGitHubUsers = async () => { - if (givenUsersList) { - const newMap = new Map(); - await Promise.all( - givenUsersList.map(async (classroomUser) => { - await fetchUser(classroomUser.github_username) - .then((userResponse: IGitHubUserResponse) => { - newMap.set(classroomUser, userResponse.user); - }) - .catch((_) => { - // do nothing - }); - }) - ); - setUserMap(newMap); - } - }; - - void loadGitHubUsers(); - }, [givenUsersList]); + const userQueries = useQueries({ + queries: givenUsersList.map((classroomUser) => ({ + queryKey: ['user', classroomUser.github_username], + queryFn: () => fetchUser(classroomUser.github_username), + staleTime: 1000 * 60 * 60, // 1 hour + cacheTime: 1000 * 60 * 60 * 24, // 24 hours + })) + }); let userIcons: React.ReactNode[] = []; const MAX_USERS_TO_SHOW = 3; if (givenUsersList && givenUsersList.length > 0) { const usersToShow = givenUsersList.slice(0, MAX_USERS_TO_SHOW); - userIcons = usersToShow.map((classroomUser, index) => { - const githubUser = userMap.get(classroomUser); + userIcons = usersToShow.map((_, index) => { + const userQuery = userQueries[index]; + const githubUser = userQuery.data?.github_user; + + const showPlaceholder = userQuery.isLoading || !userQuery.data || !githubUser; + return (
- {!githubUser ? ( + {showPlaceholder ? (
) : ( { const [user, setUser] = useState(null); useEffect(() => { const fetchUser = async () => { await fetchCurrentUser() - .then((user: IGitHubUser | null) => { - setUser(user); + .then((data: IUserResponse) => { + setUser(data.github_user); }) .catch((error: unknown) => { console.error("Error fetching user data:", error); diff --git a/frontend/src/contexts/auth.tsx b/frontend/src/contexts/auth.tsx index 962c26c2..9a37ddf4 100644 --- a/frontend/src/contexts/auth.tsx +++ b/frontend/src/contexts/auth.tsx @@ -1,4 +1,5 @@ -import { useState, createContext, useLayoutEffect, useContext } from "react"; +import { useState, createContext, useContext, useEffect } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; import { logout as logoutApi } from "@/api/auth"; import { SelectedClassroomContext } from "./selectedClassroom"; @@ -21,49 +22,56 @@ export const AuthContext = createContext({ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { - const [currentUser, setCurrentUser] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); - const [loading, setLoading] = useState(true); const { setSelectedClassroom } = useContext(SelectedClassroomContext); - useLayoutEffect(() => { - fetchCurrentUser() - .then((user: IGitHubUser | null) => { - if (user) { - setIsLoggedIn(true); - setCurrentUser(user); - } else { - setIsLoggedIn(false); - } - }) - .catch((_: unknown) => { - setIsLoggedIn(false); - setCurrentUser(null); - }) - .finally(() => { - setLoading(false); - }); - }, []); + const { data: user, isLoading } = useQuery({ + queryKey: ['currentUser'], + queryFn: fetchCurrentUser, + select: (data: IUserResponse) => { + return data; + }, + retry: false, + staleTime: 0, + gcTime: 0, + refetchOnWindowFocus: true, + refetchOnReconnect: true + }); + + useEffect(() => { + setIsLoggedIn(!!user); + }, [user]); + + const logoutMutation = useMutation({ + mutationFn: logoutApi, + onSuccess: () => { + setSelectedClassroom(null); + setIsLoggedIn(false); + }, + retry: false // Don't retry logout operations + }); const login = () => { setIsLoggedIn(true); }; const logout = () => { - logoutApi() - .then(() => { - setSelectedClassroom(null); - setIsLoggedIn(false); - }) - .catch((_: Error) => {}); + logoutMutation.mutate(); }; - if (loading) { + if (isLoading) { return null; } return ( - + {children} ); diff --git a/frontend/src/global.css b/frontend/src/global.css index 53c635ef..f002bcc3 100644 --- a/frontend/src/global.css +++ b/frontend/src/global.css @@ -105,6 +105,13 @@ a { font-size: var(--body-font-size); } +.error { + color: var(--red); + font-size: var(--font-size-xs); + margin-top: 5px; + text-align: center; +} + /* Custom scroll bar flex properties and styling */ .simplebar-wrapper { flex-grow: 1; diff --git a/frontend/src/hooks/useClassroomUser.tsx b/frontend/src/hooks/useClassroomUser.tsx index 8b5081c0..4f32f142 100644 --- a/frontend/src/hooks/useClassroomUser.tsx +++ b/frontend/src/hooks/useClassroomUser.tsx @@ -1,42 +1,24 @@ -import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; import { getCurrentClassroomUser } from "@/api/classrooms"; export function useClassroomUser(classroomId?: number) { - const [classroomUser, setClassroomUser] = useState( - null - ); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchClassroomUser = async () => { - if (classroomId) { - await getCurrentClassroomUser(classroomId) - .then((user) => { - if (user.classroom_id === classroomId) { - setClassroomUser(user); - setError(null); - } else { - setError(new Error("User is not in the specified classroom")); - setClassroomUser(null); - } - }) - .catch((err) => { - setError(err); - setClassroomUser(null); - }) - .finally(() => { - setLoading(false); - }); - } else { - setClassroomUser(null); - setError(null); - setLoading(false); + const { data: classroomUser, error, isLoading } = useQuery({ + queryKey: ['classroomUser', classroomId], + queryFn: async () => { + if (!classroomId) return null; + const user = await getCurrentClassroomUser(classroomId); + if (user.classroom_id !== classroomId) { + throw new Error("User is not in the specified classroom"); } - }; - - fetchClassroomUser(); - }, [classroomId]); + return user; + }, + enabled: !!classroomId, + retry: false + }); - return { classroomUser, error, loading }; + return { + classroomUser: classroomUser || null, + error: error as Error | null, + loading: isLoading + }; } diff --git a/frontend/src/hooks/useClassroomUsersList.tsx b/frontend/src/hooks/useClassroomUsersList.tsx index 4b00a9c4..2a4be45b 100644 --- a/frontend/src/hooks/useClassroomUsersList.tsx +++ b/frontend/src/hooks/useClassroomUsersList.tsx @@ -1,35 +1,19 @@ -import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; import { getClassroomUsers } from "@/api/classrooms"; export function useClassroomUsersList(classroomId?: number) { - const [classroomUsers, setClassroomUsers] = useState([]); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); + const { data: classroomUsers, error, isLoading } = useQuery({ + queryKey: ['classroomUsers', classroomId], + queryFn: async () => { + if (!classroomId) return []; + return await getClassroomUsers(classroomId); + }, + enabled: !!classroomId + }); - useEffect(() => { - const fetchClassroomUsers = async () => { - if (classroomId) { - await getClassroomUsers(classroomId) - .then((users) => { - setClassroomUsers(users); - setError(null); - }) - .catch((_) => { - setError(new Error("Failed to fetch classroom users")); - setClassroomUsers([]); - }) - .finally(() => { - setLoading(false); - }); - } else { - setClassroomUsers([]); - setError(null); - setLoading(false); - } - }; - - fetchClassroomUsers(); - }, [classroomId]); - - return { classroomUsers, error, loading }; + return { + classroomUsers: classroomUsers || [], + error: error as Error | null, + loading: isLoading + }; } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 04002a9e..8f9edbeb 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -8,6 +8,9 @@ import { Outlet, useLocation, } from "react-router-dom"; +import { QueryClient } from '@tanstack/react-query' +import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'; +import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'; import * as Pages from "./pages"; import Layout from "./components/Layout"; @@ -34,90 +37,105 @@ const PrivateRoute = () => { return ; }; +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 30 * 60 * 1000, // 30 minutes + refetchOnMount: 'always', + }, + }, +}); + +const persister = createSyncStoragePersister({ + storage: window.localStorage, +}); + export default function App(): React.JSX.Element { return ( - - - - - {/******* LANDING PAGE & OAUTH CALLBACK *******/} - } /> - } /> + + + + + + {/******* LANDING PAGE & OAUTH CALLBACK *******/} + } /> + } /> - {/******* APP ROUTES: AUTHENTICATED USER *******/} - }> - {/******* CLASS SELECTION: PRE-APP ACCESS STEP *******/} + {/******* APP ROUTES: AUTHENTICATED USER *******/} + }> + {/******* CLASS SELECTION: PRE-APP ACCESS STEP *******/} - - } /> - } /> - } /> - } - /> - } /> - } /> - - }> - } - /> - - - }> - } - /> - } - /> - - {/******* CLASS SELECTED: INNER APP *******/} - }> - } /> - } - /> - } /> - } - /> - } - /> + + } /> + } /> + } /> + } + /> + } /> + } /> + + }> + } + /> + - } /> - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - } /> - } /> + }> + } + /> + } + /> + + {/******* CLASS SELECTED: INNER APP *******/} + }> + } /> + } + /> + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } + /> + } + /> + } /> + } /> + } /> + } /> + - - {/******* 404 CATCH ALL *******/} - } /> - - - - + {/******* 404 CATCH ALL *******/} + } /> + + + + + ); } diff --git a/frontend/src/pages/Assignments/Assignment/index.tsx b/frontend/src/pages/Assignments/Assignment/index.tsx index e392f76c..b053197a 100644 --- a/frontend/src/pages/Assignments/Assignment/index.tsx +++ b/frontend/src/pages/Assignments/Assignment/index.tsx @@ -1,7 +1,7 @@ import { useLocation, useParams, Link } from "react-router-dom"; import { MdEdit, MdEditDocument } from "react-icons/md"; import { FaGithub } from "react-icons/fa"; -import { useContext, useEffect, useState } from "react"; +import { useContext } from "react"; import { Chart as ChartJS, registerables } from "chart.js"; import { Bar, Doughnut } from "react-chartjs-2"; import ChartDataLabels from "chartjs-plugin-datalabels"; @@ -25,6 +25,7 @@ import { Table, TableCell, TableRow } from "@/components/Table"; import Button from "@/components/Button"; import MetricPanel from "@/components/Metrics/MetricPanel"; import Metric from "@/components/Metrics"; +import { useQuery } from "@tanstack/react-query"; import Pill from "@/components/Pill"; import "./styles.css"; import { StudentWorkState } from "@/types/enums"; @@ -35,143 +36,81 @@ ChartJS.register(ChartDataLabels); const Assignment: React.FC = () => { const location = useLocation(); - const [assignment, setAssignment] = useState(); - const [assignmentTemplate, setAssignmentTemplate] = useState(); - const [studentWorks, setStudentAssignment] = useState([]); const { selectedClassroom } = useContext(SelectedClassroomContext); const { id } = useParams(); - const [inviteLink, setInviteLink] = useState(""); - const [linkError, setLinkError] = useState(null); const base_url: string = import.meta.env.VITE_PUBLIC_FRONTEND_DOMAIN as string; - const [acceptanceMetrics, setAcceptanceMetrics] = useState({ - labels: ["Not Accepted", "Accepted", "Started", "Submitted", "In Grading"], - datasets: [ - { - backgroundColor: [ - "#f83b5c", - "#50c878", - "#fece5a", - "#7895cb", - "#219386", - ], - data: [], - }, - ], - }); - const [gradedMetrics, setGradedMetrics] = useState({ - labels: ["Graded", "Ungraded"], - datasets: [ - { - backgroundColor: ["#219386", "#e5e7eb"], - data: [], - }, - ], - }); - - useEffect(() => { - if (!selectedClassroom || !id) return; - - // populate acceptance metrics - getAssignmentAcceptanceMetrics(selectedClassroom.id, Number(id)).then( - (metrics) => { - acceptanceMetrics.datasets[0].data = [ - metrics.not_accepted, - metrics.accepted, - metrics.started, - metrics.submitted, - metrics.in_grading, - ]; - setAcceptanceMetrics(acceptanceMetrics); + const { data: assignment } = useQuery({ + queryKey: ['assignment', selectedClassroom?.id, id], + queryFn: async () => { + if (!selectedClassroom?.id || !id) return null; + if (location.state?.assignment) { + return location.state.assignment; } - ); - - // populate graded status metrics - getAssignmentGradedMetrics(selectedClassroom.id, Number(id)).then( - (metrics) => { - gradedMetrics.datasets[0].data = [metrics.graded, metrics.ungraded]; - setGradedMetrics(gradedMetrics); - } - ); - }, [selectedClassroom]); - - useEffect(() => { - // check if assignment has been passed through - if (location.state) { - setAssignment(location.state.assignment); - const a: IAssignmentOutline = location.state.assignment; - - // sync student assignments - if (selectedClassroom !== null && selectedClassroom !== undefined) { - getAssignmentTemplate(selectedClassroom.id, a.id) - .then(assignmentTemplate => { - setAssignmentTemplate(assignmentTemplate); - }) - .catch(_ => { - // do nothing - }); + return await getAssignmentIndirectNav(selectedClassroom.id, +id); + }, + enabled: !!selectedClassroom?.id && !!id + }); - (async () => { - try { - const studentWorks = await getStudentWorks( - selectedClassroom.id, - a.id - ); - if (studentWorks !== null && studentWorks !== undefined) { - setStudentAssignment(studentWorks); - } - } catch (_) { - // do nothing - } - })(); - } - } else { - // fetch the assignment from backend - if (id && selectedClassroom !== null && selectedClassroom !== undefined) { - (async () => { - try { - const fetchedAssignment = await getAssignmentIndirectNav( - selectedClassroom.id, - +id - ); - if (fetchedAssignment !== null && fetchedAssignment !== undefined) { - setAssignment(fetchedAssignment); - const studentWorks = await getStudentWorks( - selectedClassroom.id, - fetchedAssignment.id - ); - if (studentWorks !== null && studentWorks !== undefined) { - setStudentAssignment(studentWorks); - } - } - } catch (_) { - // do nothing - } - })(); - } - } - }, [selectedClassroom]); + const { data: studentWorks = [] } = useQuery({ + queryKey: ['studentWorks', selectedClassroom?.id, assignment?.id], + queryFn: async () => { + if (!selectedClassroom?.id || !assignment?.id) return []; + return await getStudentWorks(selectedClassroom.id, assignment.id); + }, + enabled: !!selectedClassroom?.id && !!assignment?.id + }); - useEffect(() => { - const generateInviteLink = async () => { - if (!assignment) return; + const { data: inviteLink = "", error: linkError } = useQuery({ + queryKey: ['assignmentToken', selectedClassroom?.id, assignment?.id], + queryFn: async () => { + if (!selectedClassroom?.id || !assignment?.id) return ""; + const tokenData = await postAssignmentToken(selectedClassroom.id, assignment.id); + return `${base_url}/app/token/assignment/accept?token=${tokenData.token}`; + }, + enabled: !!selectedClassroom?.id && !!assignment?.id + }); - try { - if (!selectedClassroom) return; - const tokenData = await postAssignmentToken( - selectedClassroom.id, - assignment.id - ); - const url = `${base_url}/app/token/assignment/accept?token=${tokenData.token}`; - setInviteLink(url); - } catch (_) { - setLinkError("Failed to generate assignment invite link"); - } - }; + const { data: assignmentTemplate } = useQuery({ + queryKey: ['assignmentTemplate', selectedClassroom?.id, assignment?.id], + queryFn: async () => { + if (!selectedClassroom?.id || !assignment?.id) return null; + return await getAssignmentTemplate(selectedClassroom.id, assignment.id); + }, + enabled: !!selectedClassroom?.id && !!assignment?.id + }); - generateInviteLink(); - }, [assignment]); + const { data: acceptanceMetrics } = useQuery({ + queryKey: ['acceptanceMetrics', selectedClassroom?.id, id], + queryFn: async () => { + if (!selectedClassroom?.id || !id) return null; + const metrics = await getAssignmentAcceptanceMetrics(selectedClassroom.id, Number(id)); + return { + labels: ["Not Accepted", "Accepted", "Started", "Submitted", "In Grading"], + datasets: [{ + backgroundColor: ["#f83b5c", "#50c878", "#fece5a", "#7895cb", "#219386"], + data: [metrics.not_accepted, metrics.accepted, metrics.started, metrics.submitted, metrics.in_grading] + }] + }; + }, + enabled: !!selectedClassroom?.id && !!id + }); + const { data: gradedMetrics } = useQuery({ + queryKey: ['gradedMetrics', selectedClassroom?.id, id], + queryFn: async () => { + if (!selectedClassroom?.id || !id) return null; + const metrics = await getAssignmentGradedMetrics(selectedClassroom.id, Number(id)); + return { + labels: ["Graded", "Ungraded"], + datasets: [{ + backgroundColor: ["#219386", "#e5e7eb"], + data: [metrics.graded, metrics.ungraded] + }] + }; + }, + enabled: !!selectedClassroom?.id && !!id + }); const assignmentTemplateLink = assignmentTemplate ? `https://github.com/${assignmentTemplate.template_repo_owner}/${assignmentTemplate.template_repo_name}` : ""; const firstCommitDate = studentWorks.reduce((earliest, work) => { @@ -224,7 +163,7 @@ const Assignment: React.FC = () => {

Assignment Link

- {linkError &&

{linkError}

} + {linkError &&

Failed to generate assignment invite link

}
@@ -243,87 +182,91 @@ const Assignment: React.FC = () => { title="Grading Status" className="Assignment__metricsChart Assignment__metricsChart--graded" > - {}, - display: true, - position: "bottom", - labels: { - usePointStyle: true, + {gradedMetrics && ( + {}, + display: true, + position: "bottom", + labels: { + usePointStyle: true, + font: { + size: 12, + }, + }, + }, + datalabels: { + color: ["#fff", "#000"], font: { size: 12, }, }, - }, - datalabels: { - color: ["#fff", "#000"], - font: { - size: 12, + tooltip: { + enabled: false, }, }, - tooltip: { - enabled: false, - }, - }, - cutout: "65%", - borderColor: "transparent", - }} - /> + cutout: "65%", + borderColor: "transparent", + }} + /> + )} - + }} + /> + )}
@@ -338,7 +281,7 @@ const Assignment: React.FC = () => { {studentWorks && studentWorks.length > 0 && - studentWorks.map((sa, i) => ( + studentWorks.map((sa: IStudentWork, i: number) => ( {sa.work_state !== StudentWorkState.NOT_ACCEPTED ? ( diff --git a/frontend/src/pages/Assignments/AssignmentRubric/index.tsx b/frontend/src/pages/Assignments/AssignmentRubric/index.tsx index 99e20eb7..3a2d0fe0 100644 --- a/frontend/src/pages/Assignments/AssignmentRubric/index.tsx +++ b/frontend/src/pages/Assignments/AssignmentRubric/index.tsx @@ -6,6 +6,7 @@ import { getRubric, getRubricsInClassroom } from "@/api/rubrics"; import Button from "@/components/Button"; import { Table, TableCell, TableRow } from "@/components/Table"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; +import LoadingSpinner from "@/components/LoadingSpinner"; import { getAssignmentRubric } from "@/api/assignments"; import SubPageHeader from "@/components/PageHeader/SubPageHeader"; @@ -87,7 +88,7 @@ const AssignmentRubric: React.FC = () => { )} {loading && !errorState && ( -
Loading...
+ )} {assignment && !errorState && !loading && ( diff --git a/frontend/src/pages/Callback/index.tsx b/frontend/src/pages/Callback/index.tsx index d9d3cc93..2484e07c 100644 --- a/frontend/src/pages/Callback/index.tsx +++ b/frontend/src/pages/Callback/index.tsx @@ -1,9 +1,9 @@ import { AuthContext } from "@/contexts/auth"; import { useContext, useEffect, useRef } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; -import ClipLoader from "react-spinners/ClipLoader"; import "./styles.css"; import { sendCode } from "@/api/auth"; +import LoadingSpinner from "@/components/LoadingSpinner"; const Callback: React.FC = () => { const [searchParams] = useSearchParams(); @@ -48,7 +48,7 @@ const Callback: React.FC = () => { return (
- +

Logging in...

); diff --git a/frontend/src/pages/Classrooms/Create/index.tsx b/frontend/src/pages/Classrooms/Create/index.tsx index 6ad9901e..c893a151 100644 --- a/frontend/src/pages/Classrooms/Create/index.tsx +++ b/frontend/src/pages/Classrooms/Create/index.tsx @@ -1,62 +1,49 @@ -import React, { useContext, useEffect, useState } from "react"; -import { useNavigate, Link } from "react-router-dom"; +import React, { useContext } from "react"; +import { useNavigate, Link, useLocation } from "react-router-dom"; import { getClassroomNames, postClassroom } from "@/api/classrooms"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; import { getOrganizationDetails } from "@/api/organizations"; -import useUrlParameter from "@/hooks/useUrlParameter"; import Panel from "@/components/Panel"; import Button from "@/components/Button"; +import { useMutation, useQuery } from "@tanstack/react-query"; import "./styles.css"; import Input from "@/components/Input"; import GenericDropdown from "@/components/Dropdown"; +import LoadingSpinner from "@/components/LoadingSpinner"; const ClassroomCreation: React.FC = () => { - const [name, setName] = useState(""); - const [organization, setOrganization] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const orgID = useUrlParameter("org_id"); - const [predefinedClassroomNames, setPredefinedClassroomNames] = useState([]); - const [showCustomNameInput, setShowCustomNameInput] = useState(false); + const [name, setName] = React.useState(""); + const [showCustomNameInput, setShowCustomNameInput] = React.useState(false); const { setSelectedClassroom } = useContext(SelectedClassroomContext); const navigate = useNavigate(); + const location = useLocation(); + const orgID = location.state?.orgID; - useEffect(() => { - const fetchClassroomNames = async () => { - try { - const names = await getClassroomNames(); - setPredefinedClassroomNames([...names, "Custom"]); - if (names.length > 0) { - setName(names[0]); - } - } catch (error) { - console.error("Failed to fetch classroom names:", error); + const { data: predefinedClassroomNames = [], isError: isNamesError } = useQuery({ + queryKey: ['classroomNames'], + queryFn: async () => { + const names = await getClassroomNames(); + if (names.length > 0 && !name) { + setName(names[0]); } - }; - - fetchClassroomNames(); - }, []); + return names; + } + }); - useEffect(() => { - const fetchOrganizationDetails = async () => { - if (orgID) { - setLoading(true); - await getOrganizationDetails(orgID) - .then((org) => { - setOrganization(org); - }) - .catch((_) => { - setError("Failed to fetch organization details. Please try again."); - }) - .finally(() => { - setLoading(false); - }); - } - }; + const { data: organization, isLoading: isOrgLoading, error: orgError } = useQuery({ + queryKey: ['organization', orgID], + queryFn: () => getOrganizationDetails(orgID), + enabled: !!orgID + }); - fetchOrganizationDetails(); - }, [orgID, navigate]); + const createClassroomMutation = useMutation({ + mutationFn: postClassroom, + onSuccess: (createdClassroom: IClassroom) => { + setSelectedClassroom(createdClassroom); + navigate("/app/classroom/invite-tas"); + } + }); const handleNameChange = (selected: string) => { if (selected === "Custom") { @@ -71,32 +58,23 @@ const ClassroomCreation: React.FC = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name || !organization) { - setError("Please fill in all fields."); return; } - setLoading(true); - await postClassroom({ + + createClassroomMutation.mutate({ name: name, org_id: organization.id, org_name: organization.login, - }) - .then((createdClassroom) => { - setSelectedClassroom(createdClassroom); - navigate("/app/classroom/invite-tas"); - }) - .catch((_) => { - setError("Failed to create classroom. Please try again."); - }) - .finally(() => { - setLoading(false); - }); + }); }; + const allClassroomNames = [...predefinedClassroomNames, "Custom"]; + return (
- {loading ? ( -

Loading...

+ {isOrgLoading ? ( + ) : (
{ value={organization ? organization.login : ""} /> - {predefinedClassroomNames.length > 0 && ( + {allClassroomNames.length > 0 && ( ({ value: option, label: option }))} + options={allClassroomNames.map(option => ({ value: option, label: option }))} onChange={handleNameChange} /> )} - {(showCustomNameInput || predefinedClassroomNames.length === 0) && ( + {(showCustomNameInput || allClassroomNames.length === 0) && ( { /> )} - {error &&

{error}

} + {(createClassroomMutation.error || orgError || isNamesError) && ( +

+ {createClassroomMutation.error + ? "Failed to create classroom. Please try again." + : orgError + ? "Failed to fetch organization details. Please try again." + : "Failed to fetch classroom names. Please try again."} +

+ )} + {!organization && (

@@ -137,7 +124,13 @@ const ClassroomCreation: React.FC = () => {

)}
- + diff --git a/frontend/src/pages/Classrooms/InviteStudents/index.tsx b/frontend/src/pages/Classrooms/InviteStudents/index.tsx index 8c399b90..b4a1b2b0 100644 --- a/frontend/src/pages/Classrooms/InviteStudents/index.tsx +++ b/frontend/src/pages/Classrooms/InviteStudents/index.tsx @@ -1,38 +1,27 @@ import Panel from "@/components/Panel"; import Button from "@/components/Button"; import CopyLink from "@/components/CopyLink"; -import { useState, useContext, useEffect } from "react"; +import { useContext } from "react"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; import { postClassroomToken } from "@/api/classrooms"; +import { useQuery } from "@tanstack/react-query"; import "../styles.css"; const InviteStudents: React.FC = () => { const { selectedClassroom } = useContext(SelectedClassroomContext); - const [link, setLink] = useState(""); const base_url: string = import.meta.env .VITE_PUBLIC_FRONTEND_DOMAIN as string; - const [error, setError] = useState(null); - useEffect(() => { - const handleCreateToken = async () => { - if (!selectedClassroom) { - return; - } - await postClassroomToken(selectedClassroom.id, "STUDENT") - .then((data: ITokenResponse) => { - const url = `${base_url}/app/token/classroom/join?token=${data.token}`; - setLink(url); - }) - .catch((_) => { - setError("Failed to generate invite URL. Please try again."); - }); - }; - - if (selectedClassroom) { - handleCreateToken(); - } - }, [selectedClassroom]); + const { data: tokenData, error } = useQuery({ + queryKey: ['classroomToken', selectedClassroom?.id], + queryFn: async () => { + if (!selectedClassroom?.id) return null; + const data = await postClassroomToken(selectedClassroom.id, "STUDENT"); + return `${base_url}/app/token/classroom/join?token=${data.token}`; + }, + enabled: !!selectedClassroom?.id + }); return ( @@ -46,8 +35,8 @@ const InviteStudents: React.FC = () => { }
- - {error &&

{error}

} + + {error &&

Failed to generate invite URL. Please try again.

}
diff --git a/frontend/src/pages/Classrooms/InviteTAs/index.tsx b/frontend/src/pages/Classrooms/InviteTAs/index.tsx index 85fab1c1..5ada7117 100644 --- a/frontend/src/pages/Classrooms/InviteTAs/index.tsx +++ b/frontend/src/pages/Classrooms/InviteTAs/index.tsx @@ -1,38 +1,27 @@ import Panel from "@/components/Panel"; import Button from "@/components/Button"; import CopyLink from "@/components/CopyLink"; -import { useState, useContext, useEffect } from "react"; +import { useContext } from "react"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; import { postClassroomToken } from "@/api/classrooms"; +import { useQuery } from "@tanstack/react-query"; import "../styles.css"; const InviteTAs: React.FC = () => { const { selectedClassroom } = useContext(SelectedClassroomContext); - const [link, setLink] = useState(""); const base_url: string = import.meta.env .VITE_PUBLIC_FRONTEND_DOMAIN as string; - const [error, setError] = useState(null); - useEffect(() => { - const handleCreateToken = async () => { - if (!selectedClassroom) { - return; - } - await postClassroomToken(selectedClassroom.id, "TA") - .then((data: ITokenResponse) => { - const url = `${base_url}/app/token/classroom/join?token=${data.token}`; - setLink(url); - }) - .catch((_) => { - setError("Failed to generate invite URL. Please try again."); - }); - }; - - if (selectedClassroom) { - handleCreateToken(); - } - }, [selectedClassroom]); + const { data: tokenData, error } = useQuery({ + queryKey: ['classroomToken', selectedClassroom?.id], + queryFn: async () => { + if (!selectedClassroom?.id) return null; + const data = await postClassroomToken(selectedClassroom.id, "TA"); + return `${base_url}/app/token/classroom/join?token=${data.token}`; + }, + enabled: !!selectedClassroom?.id + }); return ( @@ -41,11 +30,11 @@ const InviteTAs: React.FC = () => {

Use the link below to invite TAs to your Classroom

- {"To add TA’s to your classroom, invite them using this link!"} + {"To add TA's to your classroom, invite them using this link!"}
- - {error &&

{error}

} + + {error &&

Failed to generate invite URL. Please try again.

}
diff --git a/frontend/src/pages/Classrooms/Select/index.tsx b/frontend/src/pages/Classrooms/Select/index.tsx index bfb41f43..06c77d12 100644 --- a/frontend/src/pages/Classrooms/Select/index.tsx +++ b/frontend/src/pages/Classrooms/Select/index.tsx @@ -1,54 +1,47 @@ -import React, { useContext, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import React, { useContext, useEffect } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; import "./styles.css"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; import { Link } from "react-router-dom"; import { getClassroomsInOrg } from "@/api/classrooms"; -import useUrlParameter from "@/hooks/useUrlParameter"; import { Table, TableRow, TableCell } from "@/components/Table"; import EmptyDataBanner from "@/components/EmptyDataBanner"; import Button from "@/components/Button"; import Pill from "@/components/Pill"; import { removeUnderscores } from "@/utils/text"; import { MdAdd } from "react-icons/md"; -import { ClassroomRole, OrgRole, toClassroom } from "@/types/enums"; +import { useQuery } from "@tanstack/react-query"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import { ClassroomRole, OrgRole } from "@/types/enums"; +import { toClassroom } from "@/types/enums"; const ClassroomSelection: React.FC = () => { - const [classrooms, setClassrooms] = useState([]); - const [orgRole, setOrgRole] = useState(OrgRole.MEMBER); - const orgID = useUrlParameter("org_id"); - const [loading, setLoading] = useState(true); + const location = useLocation(); const { setSelectedClassroom } = useContext(SelectedClassroomContext); - const navigate = useNavigate(); + const orgID = location.state?.orgID; useEffect(() => { - const fetchClassrooms = async () => { - if (!loading && !orgID) { - navigate("/app/organization/select"); - } else { - setLoading(true); - try { - const org_id = parseInt(orgID); - if (!isNaN(org_id)) { - const data: IClassroomUsersListResponse = await getClassroomsInOrg(org_id); - if (data.classroom_users) { - setClassrooms(data.classroom_users); - } - if (data.org_role) { - setOrgRole(data.org_role); - } - } - } catch (_) { - // do nothing - } finally { - setLoading(false); - } + if (!orgID) { + console.log("No organization ID provided. Redirecting to organization selection."); + navigate("/app/organization/select"); + } + }, [orgID, navigate]); + + const { data, isLoading, error } = useQuery({ + queryKey: ['classrooms', orgID], + queryFn: async () => { + if (!orgID || isNaN(Number(orgID))) { + throw new Error("Invalid organization ID"); } - }; + return getClassroomsInOrg(Number(orgID)); + }, + enabled: !!orgID && !isNaN(Number(orgID)), + retry: false + }); - void fetchClassrooms(); - }, [orgID]); + const classrooms = data?.classroom_users || []; + const orgRole = data?.org_role || OrgRole.MEMBER; const handleClassroomSelect = (classroomUser: IClassroomUser) => { const classroom: IClassroom = toClassroom(classroomUser); @@ -62,9 +55,16 @@ const ClassroomSelection: React.FC = () => {

Your Classrooms

- {/* If the screen is loading, display a message, else render table */} - {loading ? ( -

Loading...

+ {isLoading ? ( + <> +

+ + + +

+ + ) : error ? ( +

Error loading classrooms: {error instanceof Error ? error.message : "Unknown error"}

) : ( <> @@ -75,14 +75,16 @@ const ClassroomSelection: React.FC = () => { {orgRole === OrgRole.ADMIN && (
-
- )} + )}
- {/* If the org has classrooms, populate table, else display a message TODO make alert for no classes*/} {hasClassrooms && ( classrooms.map((classroomUser, i) => ( @@ -121,7 +123,8 @@ const ClassroomSelection: React.FC = () => {

Please create a new classroom to get started. - diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx index e595a7ec..5fa5c852 100644 --- a/frontend/src/pages/Dashboard/index.tsx +++ b/frontend/src/pages/Dashboard/index.tsx @@ -4,21 +4,57 @@ import { Table, TableRow, TableCell } from "@/components/Table"; import { MdAdd } from "react-icons/md"; import { Link, useNavigate } from "react-router-dom"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; -import { useEffect, useState, useContext } from "react"; +import { useContext, useEffect } from "react"; import { getAssignments } from "@/api/assignments"; import { formatDateTime, formatDate } from "@/utils/date"; -import { useClassroomUsersList } from "@/hooks/useClassroomUsersList"; +import { useClassroomUser } from "@/hooks/useClassroomUser"; +import { useQuery } from "@tanstack/react-query"; import BreadcrumbPageHeader from "@/components/PageHeader/BreadcrumbPageHeader"; import Button from "@/components/Button"; import MetricPanel from "@/components/Metrics/MetricPanel"; +import { getClassroomUsers } from "@/api/classrooms"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import EmptyDataBanner from "@/components/EmptyDataBanner"; import Metric from "@/components/Metrics"; const Dashboard: React.FC = () => { - const [assignments, setAssignments] = useState([]); const { selectedClassroom } = useContext(SelectedClassroomContext); - const { classroomUsers: classroomUsersList } = useClassroomUsersList( - selectedClassroom?.id - ); + const { + classroomUser, + error: classroomUserError, + loading: loadingCurrentClassroomUser, + } = useClassroomUser(selectedClassroom?.id); + + const { + data: classroomUsersList = [], + error: classroomUsersError, + isLoading: classroomUsersLoading + } = useQuery({ + queryKey: ['classroomUsers', selectedClassroom?.id], + queryFn: () => { + if (!selectedClassroom?.id) { + throw new Error('No classroom selected'); + } + return getClassroomUsers(selectedClassroom.id); + }, + enabled: !!selectedClassroom?.id, + }); + + const { + data: assignments = [], + error: assignmentsError, + isLoading: assignmentsLoading + } = useQuery({ + queryKey: ['assignments', selectedClassroom?.id], + queryFn: () => { + if (!selectedClassroom?.id) { + throw new Error('No classroom selected'); + } + return getAssignments(selectedClassroom.id); + }, + enabled: !!selectedClassroom?.id, + }); + const navigate = useNavigate(); const getGCD = (a: number, b: number): number => { @@ -54,24 +90,22 @@ const Dashboard: React.FC = () => { }; useEffect(() => { - const fetchAssignments = async (classroom: IClassroom) => { - if (classroom) { - getAssignments(classroom.id) - .then((assignments) => { - setAssignments(assignments); - }) - .catch((_: unknown) => { - // do nothing - }); - } - }; - - if (selectedClassroom !== null && selectedClassroom !== undefined) { - fetchAssignments(selectedClassroom).catch((_: unknown) => { - // do nothing - }); + if ( + !loadingCurrentClassroomUser && + (classroomUserError || !classroomUser) + ) { + console.log( + "Attempted to view a classroom without access. Redirecting to classroom select." + ); + navigate(`/app/organization/select`); } - }, [selectedClassroom]); + }, [ + loadingCurrentClassroomUser, + classroomUserError, + classroomUser, + selectedClassroom?.org_id, + navigate, + ]); const handleUserGroupClick = (group: string, users: IClassroomUser[]) => { if (group === "Professor") { @@ -85,112 +119,177 @@ const Dashboard: React.FC = () => { } }; + if (classroomUser?.classroom_role === "STUDENT") { + return ( +
+

Access Denied

+

+ You do not have permission to view the classroom management dashboard. +

+

Please contact your professor if you believe this is an error.

+ +
+ ); + } + + if (!selectedClassroom) { + return ( +
+

No Classroom Selected

+

Please select a classroom to continue.

+ +
+ ); + } + + if (classroomUsersError || assignmentsError) { + return ( +
+

Error Loading Dashboard

+

There was an error loading the dashboard data. Please try again later.

+ {classroomUsersError &&

Error loading users: {classroomUsersError.message}

} + {assignmentsError &&

Error loading assignments: {assignmentsError.message}

} +
+ + +
+
+ ); + } + + if (classroomUsersLoading) { + return ( +
+ +
+ ); + } + return (
- {selectedClassroom && ( - <> - - -
- -
- user.classroom_role === "STUDENT" - )} - onClick={() => - handleUserGroupClick( - "Student", - classroomUsersList.filter( - (user) => user.classroom_role === "STUDENT" - ) - ) - } - /> - - user.classroom_role === "TA" - )} - onClick={() => - handleUserGroupClick( - "TA", - classroomUsersList.filter( - (user) => user.classroom_role === "TA" - ) - ) - } - /> - - user.classroom_role === "PROFESSOR" - )} - onClick={() => - handleUserGroupClick( - "Professor", - classroomUsersList.filter( - (user) => user.classroom_role === "PROFESSOR" - ) - ) - } - /> -
- - - {formatDate(selectedClassroom.created_at ?? null)} - - - {assignments.length.toString()} - - - {getTaToStudentRatio(classroomUsersList)} - -
+ + +
+ +
+ user.classroom_role === "STUDENT" + )} + onClick={() => + handleUserGroupClick( + "Student", + classroomUsersList.filter( + (user: IClassroomUser) => user.classroom_role === "STUDENT" + ) + ) + } + /> + + user.classroom_role === "TA" + )} + onClick={() => + handleUserGroupClick( + "TA", + classroomUsersList.filter( + (user: IClassroomUser) => user.classroom_role === "TA" + ) + ) + } + /> + + user.classroom_role === "PROFESSOR" + )} + onClick={() => + handleUserGroupClick( + "Professor", + classroomUsersList.filter( + (user: IClassroomUser) => user.classroom_role === "PROFESSOR" + ) + ) + } + />
-
-
-

Assignments

-
- -
-
-
- - Assignment Name - Created Date - - {assignments.map((assignment, i: number) => ( - - - - {assignment.name} - - - {formatDateTime(assignment.created_at)} - - ))} -
+ + {formatDate(selectedClassroom.created_at ?? null)} + + + {assignments.length.toString()} + + + {getTaToStudentRatio(classroomUsersList)} + + +
+ +
+
+

Assignments

+
+
- - )} +
+ {assignments.length === 0 ? ( + +
+ {assignmentsLoading ? ( + + ) : ( +

No assignments have been created yet.

+ )} +
+ +
+ ) : ( + + + Assignment Name + Created Date + + {assignments.map((assignment: IAssignmentOutline, i: number) => ( + + + + {assignment.name} + + + {formatDateTime(assignment.created_at)} + + ))} +
+ )} +
); }; diff --git a/frontend/src/pages/Dashboard/styles.css b/frontend/src/pages/Dashboard/styles.css index 824a8413..3f6e57e6 100644 --- a/frontend/src/pages/Dashboard/styles.css +++ b/frontend/src/pages/Dashboard/styles.css @@ -41,4 +41,16 @@ &__sectionWrapper { padding: 20px; } -} \ No newline at end of file + + &__loading { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + &__horizontalButtons { + display: flex; + gap: 10px; + } +} diff --git a/frontend/src/pages/Grading/index.tsx b/frontend/src/pages/Grading/index.tsx index 528c7d16..a0cae157 100644 --- a/frontend/src/pages/Grading/index.tsx +++ b/frontend/src/pages/Grading/index.tsx @@ -1,6 +1,7 @@ import { FaChevronRight, FaChevronDown } from "react-icons/fa"; import { useNavigate } from "react-router-dom"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; import { getAssignments } from "@/api/assignments"; @@ -13,10 +14,13 @@ import { TableCell, TableDiv, } from "@/components/Table/index.tsx"; -import BreadcrumbPageHeader from "@/components/PageHeader/BreadcrumbPageHeader"; -import Button from "@/components/Button"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import EmptyDataBanner from "@/components/EmptyDataBanner"; import "./styles.css"; +import Button from "@/components/Button"; +import { MdAdd } from "react-icons/md"; +import BreadcrumbPageHeader from "@/components/PageHeader/BreadcrumbPageHeader"; import { StudentWorkState } from "@/types/enums"; interface IGradingAssignmentRow extends React.HTMLProps { @@ -28,15 +32,17 @@ const GradingAssignmentRow: React.FC = ({ children, }) => { const [collapsed, setCollapsed] = useState(true); - const [studentAssignments, setStudentAssignments] = useState( - [] - ); - const { selectedClassroom: selectedClassroom } = useContext( - SelectedClassroomContext - ); + const { selectedClassroom } = useContext(SelectedClassroomContext); const navigate = useNavigate(); + const { data: studentAssignments } = useQuery({ + queryKey: ['studentWorks', selectedClassroom?.id, assignment.id], + queryFn: () => getStudentWorks(selectedClassroom!.id, assignment.id), + enabled: !!selectedClassroom && !collapsed, + }); + const downloadGrades = () => { + if (!studentAssignments) return; const csvContent = "Student,Auto Grader Score,Manual Feedback Score,Overall Score\n" + studentAssignments @@ -67,17 +73,6 @@ const GradingAssignmentRow: React.FC = ({ URL.revokeObjectURL(url); }; - useEffect(() => { - if (!selectedClassroom) return; - getStudentWorks(selectedClassroom.id, assignment.id) - .then((studentAssignments) => { - setStudentAssignments(studentAssignments); - }) - .catch((err: unknown) => { - console.error("Error fetching student assignments:", err); - }); - }, []); - return ( <> = ({ }; const Grading: React.FC = () => { - const [assignments, setAssignments] = useState([]); - const { selectedClassroom: selectedClassroom } = useContext( - SelectedClassroomContext - ); - useEffect(() => { - if (!selectedClassroom) return; - getAssignments(selectedClassroom.id) - .then((assignments) => { - setAssignments(assignments); - }) - .catch((err: unknown) => { - console.error("Error fetching assignments:", err); - }); - }, []); + const { selectedClassroom } = useContext(SelectedClassroomContext); + + const { data: assignments, isLoading, error } = useQuery({ + queryKey: ['assignments', selectedClassroom?.id], + queryFn: () => getAssignments(selectedClassroom!.id), + enabled: !!selectedClassroom, + }); return ( - selectedClassroom && ( -
+
+ {selectedClassroom && ( + )} + {isLoading ? ( + + + + ) : error ? ( + + Error loading assignments: {error instanceof Error ? error.message : "Unknown error"} + + ) : assignments && assignments.length > 0 ? ( @@ -181,20 +179,29 @@ const Grading: React.FC = () => { Assigned Date Due Date - {assignments - ? assignments.map((assignment, i: number) => ( - - {assignment.name} - {formatDateTime(assignment.created_at)} - - {formatDateTime(assignment.main_due_date)} - - - )) - : "No assignments yet."} + {assignments && assignments.map((assignment, i: number) => ( + + {assignment.name} + {formatDateTime(assignment.created_at)} + {formatDateTime(assignment.main_due_date)} + + ))}
-
- ) + ) : ( + +
+ No assignments found. +
+ +
+ )} +
); }; diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx index b35ed0d6..38bccd68 100644 --- a/frontend/src/pages/Login/index.tsx +++ b/frontend/src/pages/Login/index.tsx @@ -1,70 +1,59 @@ import "./styles.css"; -import { Navigate, useLocation, useNavigate } from "react-router-dom"; -import { useContext, useEffect, useMemo, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; import ErrorMessage from "@/components/Error"; import { getCallbackURL } from "@/api/auth"; - -import { AuthContext } from "@/contexts/auth"; import { FaGithub } from "react-icons/fa6"; - import Button from "@/components/Button"; -import ClipLoader from "react-spinners/ClipLoader"; - -enum LoginStatus { - LOADING = "LOADING", - CALLBACK_ERRORED = "CALLBACK ERRORED", - LOGIN_ERRORED = "LOGIN ERRORED", - READY = "READY", -} +import { useContext, useEffect, useState } from "react"; +import { AuthContext } from "@/contexts/auth"; +import LoadingSpinner from "@/components/LoadingSpinner"; const Login: React.FC = () => { - const { isLoggedIn } = useContext(AuthContext); - const location = useLocation(); const navigate = useNavigate(); - const queryParams = useMemo( - () => new URLSearchParams(location.search), - [location.search] - ); + const queryParams = new URLSearchParams(location.search); const errorFromQuery = queryParams.get("error"); - const [status, setStatus] = useState(LoginStatus.LOADING); - const [error, setError] = useState(errorFromQuery); - const [callbackURL, setCallbackURL] = useState(null); + const [error, setError] = useState(null); + const { isLoggedIn } = useContext(AuthContext); - const fetchCallbackURL = async () => { - setStatus(LoginStatus.LOADING); - setError(null); - try { + // Use React Query to handle the API call + const { data: callbackData, error: callbackError, isLoading, refetch } = useQuery({ + queryKey: ['callback'], + queryFn: async () => { const resp = await getCallbackURL(); if (!resp.url) { throw new Error("Callback URL is empty"); } - setCallbackURL(resp.url); - setStatus(LoginStatus.READY); - setError(null); - } catch (_) { - setCallbackURL(null); - setStatus(LoginStatus.LOGIN_ERRORED); - setError("Error occurred while communicating with the server"); - } - }; - - useEffect(() => { - fetchCallbackURL(); - }, []); + return resp; + }, + retry: false, + }); + // Handle error from query params useEffect(() => { if (errorFromQuery) { - queryParams.delete("error"); setError(errorFromQuery); + queryParams.delete("error"); navigate({ search: queryParams.toString() }, { replace: true }); - setStatus(LoginStatus.CALLBACK_ERRORED); + } else { + setError(null); } - }, [errorFromQuery, navigate, queryParams]); + }, [errorFromQuery, queryParams]); + // Handle redirect when logged in + useEffect(() => { + if (isLoggedIn) { + setError(null); + navigate("/app/dashboard", { replace: true }); + } + }, [isLoggedIn, navigate]); + return isLoggedIn ? ( - - ) : ( +
+ +
+ ) : (
{
- {callbackURL && status !== LoginStatus.LOADING && ( - - )} - - {status === LoginStatus.LOADING && ( - - )} - - {error && ( -
- - -
- )} + {(() => { + switch (true) { + case !isLoading && !!callbackData?.url: + return ( + + ); + case isLoading: + return ( + + ); + case !!callbackError: + return ( +
+ +
+ ); + default: + return null; + } + })()}
+ {error && ( + + )}
); }; diff --git a/frontend/src/pages/Login/styles.css b/frontend/src/pages/Login/styles.css index 06dcd3cb..7ccc2aa5 100644 --- a/frontend/src/pages/Login/styles.css +++ b/frontend/src/pages/Login/styles.css @@ -22,6 +22,10 @@ flex-direction: column; align-items: center; } + + .Button { + width: 225px; + } } /*Format Logos*/ diff --git a/frontend/src/pages/Organizations/Select/index.tsx b/frontend/src/pages/Organizations/Select/index.tsx index 415a964b..8ce8db09 100644 --- a/frontend/src/pages/Organizations/Select/index.tsx +++ b/frontend/src/pages/Organizations/Select/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import "./styles.css"; import OrganizationDropdown from "@/components/Dropdown/Organization"; import Panel from "@/components/Panel"; @@ -7,47 +7,32 @@ import { getAppInstallations, getOrganizationDetails, } from "@/api/organizations"; +import { useQuery } from "@tanstack/react-query"; const OrganizationSelection: React.FC = () => { - const [orgsWithApp, setOrgsWithApp] = useState([]); - const [orgsWithoutApp, setOrgsWithoutApp] = useState([]); - const [loadingOrganizations, setLoadingOrganizations] = useState(true); const [selectedOrg, setSelectedOrg] = useState(null); - const [error, setError] = useState(null); const githubAppName = import.meta.env.VITE_GITHUB_APP_NAME; - useEffect(() => { - const fetchOrganizations = async () => { - try { - const data: IOrganizationsResponse = await getAppInstallations(); - //checking if data exists before populating and setting lists - if (data.orgs_with_app) { - setOrgsWithApp(data.orgs_with_app); - } - if (data.orgs_without_app) { - setOrgsWithoutApp(data.orgs_without_app); - } + const { data: installationsData, isLoading: loadingOrganizations, error: installationsError } = useQuery({ + queryKey: ['organizations'], + queryFn: getAppInstallations, + }); - setError(null); - } catch (_) { - setError("Error fetching organizations"); - } finally { - setLoadingOrganizations(false); - } - }; + const { error: orgDetailsError } = useQuery({ + queryKey: ['org-details', selectedOrg?.login], + queryFn: async () => { + if (!selectedOrg?.login) return null; + return await getOrganizationDetails(selectedOrg.login); + }, + enabled: !!selectedOrg?.login, + }); - void fetchOrganizations(); - }, []); + const orgsWithApp = installationsData?.orgs_with_app || []; + const orgsWithoutApp = installationsData?.orgs_without_app || []; + const error = installationsError || orgDetailsError; const handleOrganizationSelect = async (org: IOrganization) => { setSelectedOrg(org); - await getOrganizationDetails(org.login) - .then((orgDetails) => { - setSelectedOrg(orgDetails); - }) - .catch((_) => { - setError("Error fetching organization details"); - }); }; return ( @@ -66,7 +51,8 @@ const OrganizationSelection: React.FC = () => { orgsWithApp.some((org) => org.login === selectedOrg.login) && ( @@ -85,7 +71,7 @@ const OrganizationSelection: React.FC = () => { {"Don't see your organization?"} - {error &&
{error}
} + {error &&
{error instanceof Error ? error.message : "An error occurred"}
}
); diff --git a/frontend/src/pages/Rubrics/index.tsx b/frontend/src/pages/Rubrics/index.tsx index b5f44e1d..d926926e 100644 --- a/frontend/src/pages/Rubrics/index.tsx +++ b/frontend/src/pages/Rubrics/index.tsx @@ -1,40 +1,25 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext } from "react"; +import { useQuery } from "@tanstack/react-query"; import { Link } from "react-router-dom"; +import "./styles.css"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; +import BreadcrumbPageHeader from "@/components/PageHeader/BreadcrumbPageHeader"; import { getRubricsInClassroom } from "@/api/rubrics"; - import Button from "@/components/Button"; import RubricList from "@/components/RubricList"; -import BreadcrumbPageHeader from "@/components/PageHeader/BreadcrumbPageHeader"; - -import "./styles.css"; +import LoadingSpinner from "@/components/LoadingSpinner"; +import EmptyDataBanner from "@/components/EmptyDataBanner"; +import { MdAdd } from "react-icons/md"; const Rubrics: React.FC = () => { const { selectedClassroom } = useContext(SelectedClassroomContext); - const [rubrics, setRubricsData] = useState([]); - - const [loading, setLoading] = useState(false); - const [failedRurbicRetrival, setfailedRurbicRetrival] = useState(false); - useEffect(() => { - if (selectedClassroom) { - (async () => { - setLoading(true); - try { - const retrievedRubrics = await getRubricsInClassroom( - selectedClassroom.id - ); - if (retrievedRubrics !== null) { - setRubricsData(retrievedRubrics); - } - setLoading(false); - } catch (_) { - setfailedRurbicRetrival(true); - } - })(); - } - }, []); + const { data: rubrics, isLoading, error } = useQuery({ + queryKey: ['rubrics', selectedClassroom?.id], + queryFn: () => getRubricsInClassroom(selectedClassroom!.id), + enabled: !!selectedClassroom, + }); return ( selectedClassroom && ( @@ -44,25 +29,36 @@ const Rubrics: React.FC = () => { breadcrumbItems={[selectedClassroom?.name, "Rubrics"]} /> - {failedRurbicRetrival && ( + {isLoading ? ( + + + + ) : error ? ( + + Error loading rubrics: {error instanceof Error ? error.message : "Unknown error"} + + ) : (
-
Failed to get existing rubrics
-
- )} - - {!failedRurbicRetrival && loading &&
Loading...
} - - {!failedRurbicRetrival && !loading && rubrics && ( -
- {rubrics.length > 0 ? ( + {rubrics && rubrics.length > 0 ? ( ) : ( -
No Rubrics Found
+ +
+ No rubrics have been created yet. +
+ +
)} - - - + {rubrics && rubrics.length > 0 && ( + + + + )}
)} diff --git a/frontend/src/pages/Token/Apply/Generic/index.tsx b/frontend/src/pages/Token/Apply/Generic/index.tsx index 4c075057..a4ee21e6 100644 --- a/frontend/src/pages/Token/Apply/Generic/index.tsx +++ b/frontend/src/pages/Token/Apply/Generic/index.tsx @@ -1,6 +1,6 @@ +import LoadingSpinner from "@/components/LoadingSpinner"; import useUrlParameter from "@/hooks/useUrlParameter"; import { useEffect, useState } from "react"; -import ClipLoader from "react-spinners/ClipLoader"; export interface TokenHandlerConfig { useTokenFunction: (token: string) => Promise; @@ -45,7 +45,7 @@ export interface TokenHandlerConfig { return ( <> - + {loading && }

{message}

); diff --git a/frontend/src/pages/Users/index.tsx b/frontend/src/pages/Users/index.tsx index 7fd210e4..3d2805e2 100644 --- a/frontend/src/pages/Users/index.tsx +++ b/frontend/src/pages/Users/index.tsx @@ -1,7 +1,7 @@ import { sendOrganizationInvitesToRequestedUsers, sendOrganizationInviteToUser, revokeOrganizationInvite, removeUserFromClassroom, postClassroomToken, getClassroomUsers } from "@/api/classrooms"; import { SelectedClassroomContext } from "@/contexts/selectedClassroom"; import { ClassroomRole, ClassroomUserStatus } from "@/types/enums"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useState } from "react"; import SubPageHeader from "@/components/PageHeader/SubPageHeader"; import { Table, TableCell, TableRow } from "@/components/Table"; import EmptyDataBanner from "@/components/EmptyDataBanner"; @@ -11,6 +11,7 @@ import CopyLink from "@/components/CopyLink"; import Pill from "@/components/Pill"; import { removeUnderscores } from "@/utils/text"; import { useClassroomUser } from "@/hooks/useClassroomUser"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; interface GenericRolePageProps { role_label: string; @@ -26,22 +27,52 @@ const GenericRolePage: React.FC = ({ const { selectedClassroom } = useContext(SelectedClassroomContext); const { classroomUser: currentClassroomUser } = useClassroomUser(selectedClassroom?.id); const base_url: string = import.meta.env.VITE_PUBLIC_FRONTEND_DOMAIN as string; - const [link, setLink] = useState(""); const [error, setError] = useState(null); - const [users, setUsers] = useState(initialUserList); + const queryClient = useQueryClient(); + + const { data: users = [] } = useQuery({ + queryKey: ['classroomUsers', selectedClassroom?.id, role_type], + queryFn: async () => { + if (!selectedClassroom?.id) return []; + const users = await getClassroomUsers(selectedClassroom.id); + return users.filter((user: IClassroomUser) => user.classroom_role === role_type); + }, + enabled: !!selectedClassroom?.id, + initialData: initialUserList + }); + + const { data: inviteLink = "" } = useQuery({ + queryKey: ['classroomToken', selectedClassroom?.id, role_type], + queryFn: async () => { + if (!selectedClassroom?.id) return ""; + const data = await postClassroomToken(selectedClassroom.id, role_type); + return `${base_url}/app/token/classroom/join?token=${data.token}`; + }, + enabled: !!selectedClassroom?.id, + + }); const removeUserFromList = (userId: number) => { - setUsers(prevUsers => prevUsers.filter(user => user.id !== userId)); + queryClient.setQueryData( + ['classroomUsers', selectedClassroom?.id, role_type], + (oldData: IClassroomUser[] = []) => oldData.filter(user => user.id !== userId) + ); }; const addUserToList = (user: IClassroomUser) => { - setUsers(prevUsers => [...prevUsers, user]); + queryClient.setQueryData( + ['classroomUsers', selectedClassroom?.id, role_type], + (oldData: IClassroomUser[] = []) => [...oldData, user] + ); }; const handleInviteAll = async () => { await sendOrganizationInvitesToRequestedUsers(selectedClassroom!.id, role_type) .then((data: IClassroomInvitedUsersListResponse) => { - setUsers([...data.invited_users, ...data.requested_users]); + queryClient.setQueryData( + ['classroomUsers', selectedClassroom?.id, role_type], + [...data.invited_users, ...data.requested_users] + ); }) .catch((_) => { setError("Failed to invite all users. Please try again."); @@ -83,25 +114,6 @@ const GenericRolePage: React.FC = ({ }); }; - useEffect(() => { - const handleRefresh = async () => { - if (!selectedClassroom) { - setError(null); - return; - } - - try { - const users = await getClassroomUsers(selectedClassroom.id); - setUsers(users.filter((user: IClassroomUser) => user.classroom_role === role_type)); - setError(null); - } catch (_) { - setError("Failed to update classroom users"); - } - }; - - handleRefresh(); - }, [selectedClassroom]); - const getActionButton = (user: IClassroomUser) => { switch (user.status) { case ClassroomUserStatus.ACTIVE: @@ -116,30 +128,10 @@ const GenericRolePage: React.FC = ({ } }; - useEffect(() => { - const handleCreateToken = async () => { - if (!selectedClassroom) { - return; - } - await postClassroomToken(selectedClassroom.id, role_type) - .then((data: ITokenResponse) => { - const url = `${base_url}/app/token/classroom/join?token=${data.token}`; - setLink(url); - }) - .catch((_) => { - setError("Failed to generate invite URL. Please try again."); - }); - }; - - if (selectedClassroom) { - handleCreateToken(); - } - }, [selectedClassroom]) - return (
- {link && ( + {inviteLink && (

Invite {role_label + `s`}

@@ -148,7 +140,7 @@ const GenericRolePage: React.FC = ({

Warning: This will make them an admin of the organization.

} {error &&

{error}

}
- + {users.filter(user => user.status === ClassroomUserStatus.REQUESTED).length > 0 && (
diff --git a/frontend/src/types/user.d.ts b/frontend/src/types/user.d.ts index 65ac0469..aa2ba350 100644 --- a/frontend/src/types/user.d.ts +++ b/frontend/src/types/user.d.ts @@ -8,6 +8,11 @@ interface IGitHubUser { email: string | null; } +interface IUserResponse { + github_user: IGitHubUser; + user: IClassroomUser; +} + interface IGitHubUserResponse { user: IGitHubUser; }