diff --git a/.dockerignore b/.dockerignore index 58144f0..44d7443 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -**/node_modules - -go.work +**/node_modules + +go.work go.work.sum \ No newline at end of file diff --git a/.gitignore b/.gitignore index f41da6a..96d9eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ go.work.sum bin/ .env + +cmd/server/dist/ +.vscode/launch.json diff --git a/Dockerfile b/Dockerfile index 219851e..e1dbcfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,33 @@ -ARG GO_VERSION=1.22.1 - -FROM oven/bun as node-builder -WORKDIR /app -COPY front/package.json front/bun.lockb ./ -RUN bun install --frozen-lockfile -COPY front/ ./ -RUN bun run build - -FROM golang:${GO_VERSION}-alpine as go-builder -RUN apk add --update ca-certificates git tzdata -WORKDIR /app -COPY go.mod go.sum ./ -RUN go mod download -COPY ./ ./ -RUN rm -rf ./cmd/server/dist -COPY --from=node-builder /app/dist ./cmd/server/dist -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /server ./cmd/server/. - - -FROM busybox:glibc -RUN mkdir /app -RUN addgroup -S server && adduser -S -s /bin/false -G server server -WORKDIR /app -COPY --from=go-builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -COPY --from=go-builder /usr/share/zoneinfo /usr/share/zoneinfo -COPY --from=go-builder /server /app/server -RUN chown -R server:server /app -USER server - -EXPOSE 8080 -ENTRYPOINT ["/app/server"] - +ARG GO_VERSION=1.22.1 + +FROM oven/bun as node-builder +WORKDIR /app +COPY front/package.json front/bun.lockb ./ +RUN bun install --frozen-lockfile +COPY front/ ./ +RUN bun run build + +FROM golang:${GO_VERSION}-alpine as go-builder +RUN apk add --update ca-certificates git tzdata +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY ./ ./ +RUN rm -rf ./cmd/server/dist +COPY --from=node-builder /app/dist ./cmd/server/dist +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /server ./cmd/server/. + + +FROM busybox:glibc +RUN mkdir /app +RUN addgroup -S server && adduser -S -s /bin/false -G server server +WORKDIR /app +COPY --from=go-builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=go-builder /usr/share/zoneinfo /usr/share/zoneinfo +COPY --from=go-builder /server /app/server +RUN chown -R server:server /app +USER server + +EXPOSE 8080 +ENTRYPOINT ["/app/server"] + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a689d0b --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +build: + docker build --rm -t registry.lehouerou.net/sorareplus:latest . + +push: build + docker push registry.lehouerou.net/sorareplus:latest + +deploy: push + ssh srv03 'docker pull registry.lehouerou.net/sorareplus; cd services/sorare; docker compose up -d sorareplus' + +dbup: + goose -dir=./db/migrations postgres "host=192.168.1.250 port=5436 user=sorare password=sorare dbname=sorare sslmode=disable" up + +dbdown: + goose -dir=./db/migrations postgres "host=192.168.1.250 port=5436 user=sorare password=sorare dbname=sorare sslmode=disable" down + +.PHONY: build push deploy dbup diff --git a/build.ps1 b/build.ps1 index 3f45861..1428bcd 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,7 +1,7 @@ -docker build --rm -t registry.lehouerou.net/sorareplus:latest . -if($?) -{ - docker push registry.lehouerou.net/sorareplus:latest - ssh srv03 'docker pull registry.lehouerou.net/sorareplus; cd services/sorare; docker compose up -d sorareplus' -} - +docker build --rm -t registry.lehouerou.net/sorareplus:latest . +if($?) +{ + docker push registry.lehouerou.net/sorareplus:latest + ssh srv03 'docker pull registry.lehouerou.net/sorareplus; cd services/sorare; docker compose up -d sorareplus' +} + diff --git a/cmd/server/root.go b/cmd/server/root.go index dd2bfdd..5c2d619 100644 --- a/cmd/server/root.go +++ b/cmd/server/root.go @@ -169,6 +169,12 @@ func run(cmd *cobra.Command, _ []string) error { player := api.Group("/player") player.GET("/:slug", s.GetPlayer) player.GET("/:slug/games", s.GetPlayerGames) + + team := api.Group("/team") + team.GET("/:slug", s.GetTeam) + team.GET("/:slug/roster", s.GetTeamRoster) + team.GET("/:slug/memberships", s.GetTeamMemberships) + api.GET("/zone/all", s.GetAllZones) api.GET("/competition/club", s.GetAllClubCompetitions) api.POST("/rankings/single", s.SingleRankings) @@ -360,3 +366,66 @@ func (s *ApiServer) SearchMulti(c echo.Context) error { return c.JSON(http.StatusOK, res) } + +func (s *ApiServer) GetTeam(c echo.Context) error { + ctx := c.Request().Context() + slug := c.Param("slug") + res, err := s.db.Teams.GetOne(ctx, slug, false) + if err != nil { + return err + } + return c.JSON(http.StatusOK, res) +} + +func (s *ApiServer) GetTeamRoster(c echo.Context) error { + ctx := c.Request().Context() + slug := c.Param("slug") + + team, err := s.db.Teams.GetOne(ctx, slug, true) + if err != nil { + return err + } + + roster, err := s.db.Teams.GetRoster(ctx, slug) + if err != nil { + return err + } + + roster = lo.Map(roster, func(player model.Player, _ int) model.Player { + player.GamePlayers = lo.Filter(player.GamePlayers, func(gp model.GamePlayer, _ int) bool { + return gp.TeamSlug == slug + }) + return player + }) + + var res struct { + Team model.Team `json:"team"` + Roster []model.Player `json:"roster"` + } + res.Team = team + res.Roster = roster + + return c.JSON(http.StatusOK, res) +} + +func (s *ApiServer) GetTeamMemberships(c echo.Context) error { + ctx := c.Request().Context() + slug := c.Param("slug") + memberships, err := s.db.Teams.GetMemberships(ctx, slug) + if err != nil { + return err + } + team, err := s.db.Teams.GetOne(ctx, slug, false) + if err != nil { + return err + } + + var res struct { + Team model.Team `json:"team"` + Memberships []model.Membership `json:"memberships"` + } + res.Team = team + res.Memberships = memberships + + return c.JSON(http.StatusOK, res) +} diff --git a/db/client.go b/db/client.go index 3ac2f8b..1df67f2 100644 --- a/db/client.go +++ b/db/client.go @@ -34,7 +34,7 @@ func NewClient(db *bun.DB) *Client { Games: NewGameRepository(db), Players: NewPlayerRepository(db), GamePlayers: NewGamePlayerRepository(db), - GamePlayerScores: NewRepository[model.GamePlayerScore](db, []string{"game_id", "player_slug"}), + GamePlayerScores: NewRepository[model.GamePlayerScore](db, []string{"game_id", "player_slug", "game_date"}), Zones: NewZoneRepository(db), } diff --git a/db/migrations/00002_index_optimizations.sql b/db/migrations/00002_index_optimizations.sql index 7b48b68..7052c0c 100644 --- a/db/migrations/00002_index_optimizations.sql +++ b/db/migrations/00002_index_optimizations.sql @@ -1,9 +1,9 @@ --- +goose Up -CREATE INDEX IF NOT EXISTS idx_games_date ON public.games(date); -CREATE INDEX IF NOT EXISTS idx_competitions_zone_id ON public.competitions(zone_id); -CREATE INDEX IF NOT EXISTS idx_player_field_position ON public.players(field_position); - --- +goose Down -DROP INDEX IF EXISTS idx_games_date; -DROP INDEX IF EXISTS idx_competitions_zone_id; -DROP INDEX IF EXISTS idx_player_field_position; +-- +goose Up +CREATE INDEX IF NOT EXISTS idx_games_date ON public.games(date); +CREATE INDEX IF NOT EXISTS idx_competitions_zone_id ON public.competitions(zone_id); +CREATE INDEX IF NOT EXISTS idx_player_field_position ON public.players(field_position); + +-- +goose Down +DROP INDEX IF EXISTS idx_games_date; +DROP INDEX IF EXISTS idx_competitions_zone_id; +DROP INDEX IF EXISTS idx_player_field_position; diff --git a/db/migrations/00003_card_supply.sql b/db/migrations/00003_card_supply.sql index 1ad9157..d8f208c 100644 --- a/db/migrations/00003_card_supply.sql +++ b/db/migrations/00003_card_supply.sql @@ -1,14 +1,14 @@ --- +goose Up - CREATE TABLE IF NOT EXISTS card_supplies ( - player_slug TEXT NOT NULL, - season_start_year INTEGER NOT NULL, - PRIMARY KEY (player_slug, season_start_year), - "limited" INTEGER NOT NULL, - "rare" INTEGER NOT NULL, - "super_rare" INTEGER NOT NULL, - "unique" INTEGER NOT NULL, - "last_updated" TIMESTAMPTZ NOT NULL - ); - --- +goose Down +-- +goose Up + CREATE TABLE IF NOT EXISTS card_supplies ( + player_slug TEXT NOT NULL, + season_start_year INTEGER NOT NULL, + PRIMARY KEY (player_slug, season_start_year), + "limited" INTEGER NOT NULL, + "rare" INTEGER NOT NULL, + "super_rare" INTEGER NOT NULL, + "unique" INTEGER NOT NULL, + "last_updated" TIMESTAMPTZ NOT NULL + ); + +-- +goose Down DROP TABLE IF EXISTS card_supplies; \ No newline at end of file diff --git a/db/migrations/00004_club_memberships.sql b/db/migrations/00004_club_memberships.sql index 7e31442..60680d8 100644 --- a/db/migrations/00004_club_memberships.sql +++ b/db/migrations/00004_club_memberships.sql @@ -1,14 +1,14 @@ --- +goose Up -CREATE TABLE IF NOT EXISTS club_memberships( - id TEXT PRIMARY KEY, - player_slug TEXT NOT NULL REFERENCES players(slug), - club_slug TEXT NOT NULL REFERENCES teams(slug), - start_date DATE NOT NULL, - end_date DATE -); - -CREATE INDEX IF NOT EXISTS club_memberships_player_slug_start_date_end_date_idx ON club_memberships(player_slug, start_date, end_date); - --- +goose Down -DROP INDEX IF EXISTS club_memberships_player_slug_start_date_end_date_idx; -DROP TABLE IF EXISTS club_memberships; +-- +goose Up +CREATE TABLE IF NOT EXISTS club_memberships( + id TEXT PRIMARY KEY, + player_slug TEXT NOT NULL REFERENCES players(slug), + club_slug TEXT NOT NULL REFERENCES teams(slug), + start_date DATE NOT NULL, + end_date DATE +); + +CREATE INDEX IF NOT EXISTS club_memberships_player_slug_start_date_end_date_idx ON club_memberships(player_slug, start_date, end_date); + +-- +goose Down +DROP INDEX IF EXISTS club_memberships_player_slug_start_date_end_date_idx; +DROP TABLE IF EXISTS club_memberships; diff --git a/db/migrations/00005_player_activenationalteam.sql b/db/migrations/00005_player_activenationalteam.sql index 2bff6a6..f832312 100644 --- a/db/migrations/00005_player_activenationalteam.sql +++ b/db/migrations/00005_player_activenationalteam.sql @@ -1,7 +1,7 @@ --- +goose Up -ALTER TABLE players ADD COLUMN active_national_team_slug TEXT; - --- +goose Down -ALTER TABLE players DROP COLUMN active_national_team_slug; - - +-- +goose Up +ALTER TABLE players ADD COLUMN active_national_team_slug TEXT; + +-- +goose Down +ALTER TABLE players DROP COLUMN active_national_team_slug; + + diff --git a/db/migrations/00006_rename_club_membership.sql b/db/migrations/00006_rename_club_membership.sql index e950940..62186ae 100644 --- a/db/migrations/00006_rename_club_membership.sql +++ b/db/migrations/00006_rename_club_membership.sql @@ -1,11 +1,11 @@ --- +goose Up -ALTER TABLE club_memberships RENAME TO memberships; -ALTER TABLE memberships RENAME COLUMN club_slug TO team_slug; -ALTER TABLE memberships ADD COLUMN membership_type TEXT NOT NULL DEFAULT 'club'; - - --- +goose Down -ALTER TABLE memberships RENAME TO club_memberships; -ALTER TABLE club_memberships RENAME COLUMN team_slug TO club_slug; -ALTER TABLE club_memberships DROP COLUMN membership_type; - +-- +goose Up +ALTER TABLE club_memberships RENAME TO memberships; +ALTER TABLE memberships RENAME COLUMN club_slug TO team_slug; +ALTER TABLE memberships ADD COLUMN membership_type TEXT NOT NULL DEFAULT 'club'; + + +-- +goose Down +ALTER TABLE memberships RENAME TO club_memberships; +ALTER TABLE club_memberships RENAME COLUMN team_slug TO club_slug; +ALTER TABLE club_memberships DROP COLUMN membership_type; + diff --git a/db/migrations/00007_membership_indexes.sql b/db/migrations/00007_membership_indexes.sql index 4c7f993..de207c4 100644 --- a/db/migrations/00007_membership_indexes.sql +++ b/db/migrations/00007_membership_indexes.sql @@ -1,5 +1,5 @@ --- +goose Up -CREATE INDEX IF NOT EXISTS idx_memberships_on_player_slug_start_date_end_date ON memberships(player_slug, start_date, end_date); - --- +goose Down -DROP INDEX IF EXISTS idx_memberships_on_player_slug_start_date_end_date; +-- +goose Up +CREATE INDEX IF NOT EXISTS idx_memberships_on_player_slug_start_date_end_date ON memberships(player_slug, start_date, end_date); + +-- +goose Down +DROP INDEX IF EXISTS idx_memberships_on_player_slug_start_date_end_date; diff --git a/db/migrations/00008_indexes_on_games.sql b/db/migrations/00008_indexes_on_games.sql index 82d5d84..c3c1fe7 100644 --- a/db/migrations/00008_indexes_on_games.sql +++ b/db/migrations/00008_indexes_on_games.sql @@ -1,6 +1,6 @@ --- +goose Up -CREATE INDEX IF NOT EXISTS idx_games_away_team_slug_date ON games(away_team_slug, date); -CREATE INDEX IF NOT EXISTS idx_games_home_team_slug_date ON games(home_team_slug, date); --- +goose Down -DROP INDEX IF EXISTS idx_games_away_team_slug_date; -DROP INDEX IF EXISTS idx_games_home_team_slug_date; +-- +goose Up +CREATE INDEX IF NOT EXISTS idx_games_away_team_slug_date ON games(away_team_slug, date); +CREATE INDEX IF NOT EXISTS idx_games_home_team_slug_date ON games(home_team_slug, date); +-- +goose Down +DROP INDEX IF EXISTS idx_games_away_team_slug_date; +DROP INDEX IF EXISTS idx_games_home_team_slug_date; diff --git a/db/migrations/00009_player_search.sql b/db/migrations/00009_player_search.sql index a235717..798dca2 100644 --- a/db/migrations/00009_player_search.sql +++ b/db/migrations/00009_player_search.sql @@ -1,35 +1,35 @@ --- +goose Up -CREATE EXTENSION IF NOT EXISTS pg_trgm; -CREATE EXTENSION IF NOT EXISTS unaccent; - -CREATE OR REPLACE FUNCTION immutable_unaccent(regdictionary, TEXT) - RETURNS TEXT - LANGUAGE c - IMMUTABLE PARALLEL SAFE STRICT AS -'$libdir/unaccent', -'unaccent_dict'; - -CREATE OR REPLACE FUNCTION f_unaccent(TEXT) RETURNS TEXT - immutable - strict - parallel safe - language sql -as -$$ -SELECT public.immutable_unaccent(regdictionary 'public.unaccent', $1) -$$; - -CREATE INDEX IF NOT EXISTS players_unaccent_display_name_trgm_idx - ON players USING gin (f_unaccent(display_name::text) gin_trgm_ops); - -CREATE INDEX IF NOT EXISTS teams_unaccent_display_name_trgm_idx - ON teams USING gin (f_unaccent(display_name::text) gin_trgm_ops); - --- +goose Down -DROP INDEX IF EXISTS players_unaccent_display_name_trgm_idx; -DROP INDEX IF EXISTS teams_unaccent_display_name_trgm_idx; -DROP FUNCTION IF EXISTS f_unaccent; -DROP FUNCTION IF EXISTS immutable_unaccent; -DROP EXTENSION IF EXISTS unaccent; -DROP EXTENSION IF EXISTS pg_trgm; - +-- +goose Up +CREATE EXTENSION IF NOT EXISTS pg_trgm; +CREATE EXTENSION IF NOT EXISTS unaccent; + +CREATE OR REPLACE FUNCTION immutable_unaccent(regdictionary, TEXT) + RETURNS TEXT + LANGUAGE c + IMMUTABLE PARALLEL SAFE STRICT AS +'$libdir/unaccent', +'unaccent_dict'; + +CREATE OR REPLACE FUNCTION f_unaccent(TEXT) RETURNS TEXT + immutable + strict + parallel safe + language sql +as +$$ +SELECT public.immutable_unaccent(regdictionary 'public.unaccent', $1) +$$; + +CREATE INDEX IF NOT EXISTS players_unaccent_display_name_trgm_idx + ON players USING gin (f_unaccent(display_name::text) gin_trgm_ops); + +CREATE INDEX IF NOT EXISTS teams_unaccent_display_name_trgm_idx + ON teams USING gin (f_unaccent(display_name::text) gin_trgm_ops); + +-- +goose Down +DROP INDEX IF EXISTS players_unaccent_display_name_trgm_idx; +DROP INDEX IF EXISTS teams_unaccent_display_name_trgm_idx; +DROP FUNCTION IF EXISTS f_unaccent; +DROP FUNCTION IF EXISTS immutable_unaccent; +DROP EXTENSION IF EXISTS unaccent; +DROP EXTENSION IF EXISTS pg_trgm; + diff --git a/db/migrations/00010_avgs.sql b/db/migrations/00010_avgs.sql deleted file mode 100644 index 45cd062..0000000 --- a/db/migrations/00010_avgs.sql +++ /dev/null @@ -1,116 +0,0 @@ --- +goose Up - CREATE TABLE IF NOT EXISTS player_averages ( - player_slug VARCHAR NOT NULL, - fixture_slug VARCHAR NOT NULL, - PRIMARY KEY (player_slug, fixture_slug), - l5 INTEGER, - l5r INTEGER, - l15 INTEGER, - l15r INTEGER, - l40 INTEGER, - l40r INTEGER, - gameweek INTEGER, - ds5 INTEGER, - ds15 INTEGER, - ds40 INTEGER, - aa5 INTEGER, - aa15 INTEGER, - aa40 INTEGER, - minutes15 INTEGER - ); - - CREATE INDEX player_averages_player_slug_gameweek_index ON player_averages (player_slug ASC, gameweek DESC); - - CREATE OR REPLACE PROCEDURE calc_stats (IN starting_gameweek INTEGER) LANGUAGE plpgsql AS $$ - DECLARE - fixture RECORD; - player RECORD; - startdate TIMESTAMP WITH TIME ZONE; - l5 INTEGER; - l15 INTEGER; - l40 INTEGER; - l5r INTEGER; - l15r INTEGER; - l40r INTEGER; - ds5 INTEGER; - ds15 INTEGER; - ds40 INTEGER; - aa5 INTEGER; - aa15 INTEGER; - aa40 INTEGER; - minutes15 INTEGER; - - BEGIN - - FOR fixture IN - (SELECT * FROM fixtures WHERE game_week >= starting_gameweek ORDER BY game_week) - LOOP - RAISE NOTICE 'Processing fixture: %, Game week: %', fixture.slug, fixture.game_week; - - SELECT start_date FROM fixtures WHERE game_week = fixture.game_week - 1 INTO startdate; - IF startdate IS NULL THEN - startdate = fixture.start_date - INTERVAL '3 days'; - END IF; - - RAISE NOTICE 'Start date for calculations: %', startdate; - - FOR player IN - (SELECT * FROM players) - LOOP - RAISE NOTICE 'Calculating averages for player: %', player.slug; - WITH subquery AS (SELECT row_number() OVER (ORDER BY g.date DESC) AS rn, - score, - decisive_score, - all_around_score, - minutes - FROM game_player_scores AS gps - JOIN games AS g ON g.id = gps.game_id - WHERE player_slug = player.slug - AND g.date < startdate) - select coalesce(round(avg(score) filter ( where rn <= 5 )), 0), - coalesce(round(avg(score) filter ( where rn <= 15 )), 0), - coalesce(round(avg(score) filter ( where rn <= 40 )), 0), - coalesce(round(avg(score) filter ( where rn <= 5 and minutes > 0 )), 0), - coalesce(round(avg(score) filter ( where rn <= 15 and minutes > 0 )), 0), - coalesce(round(avg(score) filter ( where rn <= 40 and minutes > 0 )), 0), - coalesce(round(avg(decisive_score) filter ( where rn <= 5 and minutes > 0 )), 0), - coalesce(round(avg(decisive_score) filter ( where rn <= 15 and minutes > 0 )), 0), - coalesce(round(avg(decisive_score) filter ( where rn <= 40 and minutes > 0 )), 0), - coalesce(round(avg(all_around_score) filter ( where rn <= 5 and minutes > 0 )), 0), - coalesce(round(avg(all_around_score) filter ( where rn <= 15 and minutes > 0 )), 0), - coalesce(round(avg(all_around_score) filter ( where rn <= 40 and minutes > 0 )), 0), - coalesce(round(avg(minutes) filter ( where rn <= 15 )), 0) - - from subquery - into l5r, l15r, l40r, l5, l15, l40, ds5, ds15, ds40, aa5, aa15, aa40, minutes15; - - RAISE NOTICE 'Inserting/updating averages for player: %, Fixture: %, Game week: %, l15: %', player.slug, fixture.slug, fixture.game_week, l15; - - INSERT INTO player_averages (player_slug, fixture_slug, gameweek, l5, l5r, l15, l15r, l40, l40r, ds5, ds15, ds40, aa5, aa15, aa40, minutes15) - VALUES (player.slug, fixture.slug, fixture.game_week, l5, l5r, l15, l15r, l40, l40r, ds5, ds15, ds40, aa5, aa15, aa40, minutes15) - ON CONFLICT (player_slug, fixture_slug) DO UPDATE SET l5 = EXCLUDED.l5, - l5r = EXCLUDED.l5r, - l15 = EXCLUDED.l15, - l15r = EXCLUDED.l15r, - l40 = EXCLUDED.l40, - l40r = EXCLUDED.l40r, - ds5 = EXCLUDED.ds5, - ds15 = EXCLUDED.ds15, - ds40 = EXCLUDED.ds40, - aa5 = EXCLUDED.aa5, - aa15 = EXCLUDED.aa15, - aa40 = EXCLUDED.aa40, - minutes15 = EXCLUDED.minutes15; - - END LOOP; - COMMIT; - END LOOP; - -END; -$$; - --- +goose Down -DROP PROCEDURE IF EXISTS calc_stats; -DROP INDEX IF EXISTS player_averages_player_slug_gameweek_index; -DROP TABLE IF EXISTS player_averages; - diff --git a/db/migrations/00010_game_date_on_game_player_scores.sql b/db/migrations/00010_game_date_on_game_player_scores.sql new file mode 100644 index 0000000..2e1a5af --- /dev/null +++ b/db/migrations/00010_game_date_on_game_player_scores.sql @@ -0,0 +1,10 @@ + +-- +goose Up +ALTER TABLE game_player_scores ADD COLUMN game_date TIMESTAMPTZ; + +CREATE INDEX IF NOT EXISTS idx_game_player_scores_player_slug_game_date ON game_player_scores (player_slug, game_date); + +-- +goose Down +ALTER TABLE game_player_scores DROP COLUMN game_date; + +DROP INDEX IF EXISTS idx_game_player_scores_player_slug_game_date; \ No newline at end of file diff --git a/db/migrations/00011_game_player_scores_hypertable.sql b/db/migrations/00011_game_player_scores_hypertable.sql new file mode 100644 index 0000000..b3b7696 --- /dev/null +++ b/db/migrations/00011_game_player_scores_hypertable.sql @@ -0,0 +1,12 @@ +-- +goose Up + +ALTER TABLE game_player_scores DROP CONSTRAINT game_player_scores_pkey; +ALTER TABLE game_player_scores ADD PRIMARY KEY (game_id, player_slug, game_date); +SELECT create_hypertable('game_player_scores', 'game_date', migrate_data => true); +CREATE INDEX IF NOT EXISTS idx_game_players_player_slug_team_slug ON game_players(player_slug, team_slug); +-- +goose Down + +DROP INDEX IF EXISTS idx_game_players_player_slug_team_slug; +ALTER TABLE game_player_scores DROP CONSTRAINT game_player_scores_pkey; +ALTER TABLE game_player_scores ADD PRIMARY KEY (game_id, player_slug, game_date); +SELECT drop_hypertable('game_player_scores', cascade => true); \ No newline at end of file diff --git a/db/player.go b/db/player.go index 03d1077..78f36a1 100644 --- a/db/player.go +++ b/db/player.go @@ -32,8 +32,6 @@ func (r *PlayerRepository) SearchByDisplayName( err := r.db.NewSelect(). Model(&players). Relation("Team"). - Relation("DomesticLeague"). - Relation("DomesticLeague.Zone"). Where("f_unaccent(player.display_name) ILIKE ?", "%"+displayName+"%"). Limit(limit). Scan(ctx) @@ -153,10 +151,15 @@ type SingleRanking struct { MoreThan20AAPercentage decimal.Decimal `bun:"more_than_20_aa_percentage" json:"moreThan20AAPercentage"` MoreThan10AAPercentage decimal.Decimal `bun:"more_than_10_aa_percentage" json:"moreThan10AAPercentage"` - TotalMinutes decimal.Decimal `bun:"total_minutes" json:"totalMinutes"` - TotalAA decimal.Decimal `bun:"total_aa" json:"totalAA"` - AAPerMin decimal.Decimal `bun:"aa_per_min" json:"aaPerMin"` - Scores []*decimal.Decimal `bun:"scores,array" json:"scores"` + TotalMinutes decimal.Decimal `bun:"total_minutes" json:"totalMinutes"` + TotalAA decimal.Decimal `bun:"total_aa" json:"totalAA"` + AAPerMin decimal.Decimal `bun:"aa_per_min" json:"aaPerMin"` + DuelWonPer90Min decimal.Decimal `bun:"duel_won_per_90_min" json:"duelWonPer90Min"` + EffectiveClearancePer90Min decimal.Decimal `bun:"effective_clearance_per_90_min" json:"effectiveClearancePer90Min"` + ShotOnTargetPer90Min decimal.Decimal `bun:"shot_on_target_per_90_min" json:"shotOnTargetPer90Min"` + AccuratePassPer90Min decimal.Decimal `bun:"accurate_pass_per_90_min" json:"accuratePassPer90Min"` + WonContestPer90Min decimal.Decimal `bun:"won_contest_per_90_min" json:"wonContestPer90Min"` + Scores []*decimal.Decimal `bun:"scores,array" json:"scores"` Player model.Player `bun:"-" json:"player"` } @@ -294,6 +297,11 @@ func (r *PlayerRepository) SingleRankings(ctx context.Context, opts SingleRankin ColumnExpr("SUM(fs.minutes_played) as total_minutes"). ColumnExpr("SUM(fs.all_around_score) as total_aa"). ColumnExpr("COALESCE(ROUND(SUM(fs.all_around_score) / NULLIF(SUM(fs.minutes_played), 0), 3), 0) AS aa_per_min"). + ColumnExpr("COALESCE(ROUND((SUM(fs.duel_won)::NUMERIC / NULLIF(SUM(fs.minutes_played), 0)) * 90, 2), 0) AS duel_won_per_90_min"). + ColumnExpr("COALESCE(ROUND((SUM(fs.effective_clearance)::NUMERIC / NULLIF(SUM(fs.minutes_played), 0)) * 90, 2), 0) AS effective_clearance_per_90_min"). + ColumnExpr("COALESCE(ROUND((SUM(fs.shot_on_target)::NUMERIC / NULLIF(SUM(fs.minutes_played), 0)) * 90, 2), 0) AS shot_on_target_per_90_min"). + ColumnExpr("COALESCE(ROUND((SUM(fs.accurate_pass)::NUMERIC / NULLIF(SUM(fs.minutes_played), 0)) * 90, 2), 0) AS accurate_pass_per_90_min"). + ColumnExpr("COALESCE(ROUND((SUM(fs.won_contest)::NUMERIC / NULLIF(SUM(fs.minutes_played), 0)) * 90, 2), 0) AS won_contest_per_90_min"). TableExpr("\"FilteredGamePlayers\" AS gp"). Join("JOIN game_player_scores AS fs ON gp.player_slug = fs.player_slug AND gp.game_id = fs.game_id"). Group("gp.player_slug") diff --git a/db/team.go b/db/team.go index ba9e0ed..63b538e 100644 --- a/db/team.go +++ b/db/team.go @@ -20,6 +20,23 @@ func NewTeamRepository(db *bun.DB) *TeamRepository { } } +func (r *TeamRepository) GetOne(ctx context.Context, slug string, includeGames bool) (model.Team, error) { + var team model.Team + req := r.db.NewSelect(). + Model(&team). + Where("team.slug = ?", slug). + Relation("Country"). + Relation("DomesticLeague"). + Relation("DomesticLeague.Zone") + if includeGames { + req = req.Relation("HomeGames"). + Relation("AwayGames") + } + + err := req.Scan(ctx) + return team, err +} + func (r *TeamRepository) GetTeamSlugsNotInDb(ctx context.Context, teamSlugs []string) ([]string, error) { var teams []model.Team err := r.db.NewSelect(). @@ -45,3 +62,30 @@ func (r *TeamRepository) SearchByDisplayName(ctx context.Context, displayName st Scan(ctx) return teams, err } + +func (r *TeamRepository) GetRoster(ctx context.Context, slug string) ([]model.Player, error) { + var players []model.Player + err := r.db.NewSelect(). + Model(&players). + Where("player.team_slug = ?", slug). + Relation("CardSupply"). + Relation("ClubMembership"). + Relation("Country"). + Relation("GamePlayers"). + Relation("GamePlayers.Score"). + Relation("GamePlayers.Game"). + Scan(ctx) + return players, err +} + +func (r *TeamRepository) GetMemberships(ctx context.Context, slug string) ([]model.Membership, error) { + var memberships []model.Membership + err := r.db.NewSelect(). + Model(&memberships). + Relation("Player"). + Relation("Player.ClubMembership"). + Relation("Player.ClubMembership.Team"). + Where("membership.team_slug = ?", slug). + Scan(ctx) + return memberships, err +} diff --git a/front/.prettierrc b/front/.prettierrc index 0538fc1..81863fd 100644 --- a/front/.prettierrc +++ b/front/.prettierrc @@ -1,13 +1,13 @@ -{ - "trailingComma": "es5", - "tabWidth": 2, - "semi": false, - "singleQuote": false, - "useTabs": false, - "plugins": [ - "@ianvs/prettier-plugin-sort-imports", - "prettier-plugin-tailwindcss" - ], - "pluginSearchDirs": ["."] -} - +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": false, + "useTabs": false, + "plugins": [ + "@ianvs/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss" + ], + "pluginSearchDirs": ["."] +} + diff --git a/front/bun.lockb b/front/bun.lockb index f2eb4d4..19b7692 100644 Binary files a/front/bun.lockb and b/front/bun.lockb differ diff --git a/front/package-lock.json b/front/package-lock.json deleted file mode 100644 index 5f83db0..0000000 --- a/front/package-lock.json +++ /dev/null @@ -1,4802 +0,0 @@ -{ - "name": "sorarebuddy", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "sorarebuddy", - "version": "0.0.0", - "dependencies": { - "@fontsource-variable/exo-2": "^5.0.18", - "@fontsource/roboto": "^5.0.12", - "@tanstack/react-query": "^5.28.9", - "@tanstack/react-query-devtools": "^5.28.10", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router": "^6.22.3", - "react-router-dom": "^6.22.3", - "react-select": "^5.8.0" - }, - "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.2.1", - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", - "@vitejs/plugin-react": "^4.2.1", - "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.5.13", - "tailwindcss": "^3.4.3", - "typescript": "^5.2.2", - "vite": "^5.2.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", - "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", - "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", - "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.1", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.1", - "@babel/parser": "^7.24.1", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", - "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", - "dependencies": { - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", - "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", - "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", - "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/@emotion/babel-plugin/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==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, - "node_modules/@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "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==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", - "dependencies": { - "@floating-ui/utils": "^0.2.1" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", - "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" - }, - "node_modules/@fontsource-variable/exo-2": { - "version": "5.0.18", - "resolved": "https://registry.npmjs.org/@fontsource-variable/exo-2/-/exo-2-5.0.18.tgz", - "integrity": "sha512-3WvBtJHnJeiTcYJ3NMVDWQVP7lV+JEuHyy/bLOUxB9RVN0GpYG8WZT2LYjIbYnKXcBNbcaXoKlvmEY+Pv0WdvA==" - }, - "node_modules/@fontsource/roboto": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.12.tgz", - "integrity": "sha512-x0o17jvgoSSbS9OZnUX2+xJmVRvVCfeaYJjkS7w62iN7CuJWtMf5vJj8LqgC7ibqIkitOHVW+XssRjgrcHn62g==" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true - }, - "node_modules/@ianvs/prettier-plugin-sort-imports": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@ianvs/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.2.1.tgz", - "integrity": "sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.24.0", - "@babel/generator": "^7.23.6", - "@babel/parser": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", - "semver": "^7.5.2" - }, - "peerDependencies": { - "@vue/compiler-sfc": "2.7.x || 3.x", - "prettier": "2 || 3" - }, - "peerDependenciesMeta": { - "@vue/compiler-sfc": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "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==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", - "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", - "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", - "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", - "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", - "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", - "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", - "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", - "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", - "cpu": [ - "ppc64le" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", - "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", - "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", - "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", - "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", - "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", - "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", - "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tanstack/query-core": { - "version": "5.28.9", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.9.tgz", - "integrity": "sha512-hNlfCiqZevr3GRVPXS3MhaGW5hjcxvCsIQ4q6ff7EPlvFwYZaS+0d9EIIgofnegDaU2BbCDlyURoYfRl5rmzow==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/query-devtools": { - "version": "5.28.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.28.10.tgz", - "integrity": "sha512-5UN629fKa5/1K/2Pd26gaU7epxRrYiT1gy+V+pW5K6hnf1DeUKK3pANSb2eHKlecjIKIhTwyF7k9XdyE2gREvQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.28.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.9.tgz", - "integrity": "sha512-vwifBkGXsydsLxFOBMe3+f8kvtDoqDRDwUNjPHVDDt+FoBetCbOWAUHgZn4k+CVeZgLmy7bx6aKeDbe3e8koOQ==", - "dependencies": { - "@tanstack/query-core": "5.28.9" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18.0.0" - } - }, - "node_modules/@tanstack/react-query-devtools": { - "version": "5.28.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.10.tgz", - "integrity": "sha512-D+SiHZTWhK2sNgBYj+xIvUOqonsKy74OLU/YHmRB5OZVLLTiekvZd12C3rKlU+WM69jid0hjEjuFqkULOMwc3A==", - "dependencies": { - "@tanstack/query-devtools": "5.28.10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.28.9", - "react": "^18.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" - }, - "node_modules/@types/react": { - "version": "18.2.73", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.73.tgz", - "integrity": "sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA==", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.2.23", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.23.tgz", - "integrity": "sha512-ZQ71wgGOTmDYpnav2knkjr3qXdAFu0vsk8Ci5w3pGAIdj7/kKAyn+VsQDhXsmzzzepAiI9leWMmubXz690AI/A==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", - "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/type-utils": "7.4.0", - "@typescript-eslint/utils": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", - "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/typescript-estree": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", - "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", - "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "7.4.0", - "@typescript-eslint/utils": "7.4.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", - "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", - "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", - "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/typescript-estree": "7.4.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", - "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.4.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001600", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", - "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "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==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "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==" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.721", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.721.tgz", - "integrity": "sha512-k1x2r6foI8iJOp+1qTxbbrrWMsOiHkzGBYwYigaq+apO1FSqtn44KTo3Sy69qt7CRr7149zTcsDvH7MUKsOuIQ==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.6.tgz", - "integrity": "sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==", - "dev": true, - "peerDependencies": { - "eslint": ">=7" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "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", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "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/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "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": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "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==", - "engines": { - "node": ">=4" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.11" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-organize-imports": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", - "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", - "dev": true, - "optional": true, - "peer": true, - "peerDependencies": { - "@volar/vue-language-plugin-pug": "^1.0.4", - "@volar/vue-typescript": "^1.0.4", - "prettier": ">=2.0", - "typescript": ">=2.9" - }, - "peerDependenciesMeta": { - "@volar/vue-language-plugin-pug": { - "optional": true - }, - "@volar/vue-typescript": { - "optional": true - } - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.13.tgz", - "integrity": "sha512-2tPWHCFNC+WRjAC4SIWQNSOdcL1NNkydXim8w7TDqlZi+/ulZYz2OouAI6qMtkggnPt7lGamboj6LcTMwcCvoQ==", - "dev": true, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig-melody": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-import-sort": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-style-order": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig-melody": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-import-sort": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-style-order": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", - "dependencies": { - "@remix-run/router": "1.15.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", - "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-select": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", - "integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==", - "dependencies": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.8.1", - "@floating-ui/dom": "^1.0.1", - "@types/react-transition-group": "^4.4.0", - "memoize-one": "^6.0.0", - "prop-types": "^15.6.0", - "react-transition-group": "^4.3.0", - "use-isomorphic-layout-effect": "^1.1.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", - "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.2", - "@rollup/rollup-android-arm64": "4.13.2", - "@rollup/rollup-darwin-arm64": "4.13.2", - "@rollup/rollup-darwin-x64": "4.13.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", - "@rollup/rollup-linux-arm64-gnu": "4.13.2", - "@rollup/rollup-linux-arm64-musl": "4.13.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", - "@rollup/rollup-linux-riscv64-gnu": "4.13.2", - "@rollup/rollup-linux-s390x-gnu": "4.13.2", - "@rollup/rollup-linux-x64-gnu": "4.13.2", - "@rollup/rollup-linux-x64-musl": "4.13.2", - "@rollup/rollup-win32-arm64-msvc": "4.13.2", - "@rollup/rollup-win32-ia32-msvc": "4.13.2", - "@rollup/rollup-win32-x64-msvc": "4.13.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" - }, - "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "^10.3.10", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", - "dev": true, - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.0", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "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/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-isomorphic-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/vite": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", - "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", - "dev": true, - "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", - "dev": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/front/src/api/card.ts b/front/src/api/card.ts index 23a89fa..cccabf2 100644 --- a/front/src/api/card.ts +++ b/front/src/api/card.ts @@ -1,9 +1,9 @@ -import Card from "../types/card" -import { Get } from "./api" - -export async function GetUserCards(userSlug: string): Promise { - const raw = await Get({ - endpoint: `/api/user/${userSlug}/cards`, - }) - return raw -} +import Card from "../types/card" +import { Get } from "./api" + +export async function GetUserCards(userSlug: string): Promise { + const raw = await Get({ + endpoint: `/api/user/${userSlug}/cards`, + }) + return raw +} diff --git a/front/src/api/competition.ts b/front/src/api/competition.ts index 74844f0..c2fc511 100644 --- a/front/src/api/competition.ts +++ b/front/src/api/competition.ts @@ -1,11 +1,11 @@ -import Competition from "../types/competition"; -import { Get } from "./api"; - -export async function GetClubCompetitions( - zones: number[], -): Promise { - return await Get({ - endpoint: `/api/competition/club`, - params: { zones: zones.join(",") }, - }); +import Competition from "../types/competition"; +import { Get } from "./api"; + +export async function GetClubCompetitions( + zones: number[], +): Promise { + return await Get({ + endpoint: `/api/competition/club`, + params: { zones: zones.join(",") }, + }); } \ No newline at end of file diff --git a/front/src/api/fixture.ts b/front/src/api/fixture.ts index 47c9657..3a69c96 100644 --- a/front/src/api/fixture.ts +++ b/front/src/api/fixture.ts @@ -1,9 +1,9 @@ -import { Fixture } from "../types/fixture" -import { Get } from "./api" - -export async function GetAvailableFixtures(): Promise { - const raw = await Get({ - endpoint: "/api/fixture/all", - }) - return raw.sort((a, b) => b.gameWeek - a.gameWeek) -} +import { Fixture } from "../types/fixture" +import { Get } from "./api" + +export async function GetAvailableFixtures(): Promise { + const raw = await Get({ + endpoint: "/api/fixture/all", + }) + return raw.sort((a, b) => b.gameWeek - a.gameWeek) +} diff --git a/front/src/api/game.ts b/front/src/api/game.ts index 0783983..e7253b5 100644 --- a/front/src/api/game.ts +++ b/front/src/api/game.ts @@ -1,16 +1,16 @@ -import { Game } from "../types/game" -import { Get } from "./api" - -export async function GetFixtureGames(fixtureSlug: string): Promise { - const raw = await Get({ - endpoint: `/api/fixture/${fixtureSlug}/games`, - }) - return raw - .sort( - (a: Game, b: Game) => - new Date(a.date).getTime() - new Date(b.date).getTime() - ) - .filter((game: Game) => { - return game.awayTeam.slug !== "" && game.homeTeam.slug !== "" - }) -} +import { Game } from "../types/game" +import { Get } from "./api" + +export async function GetFixtureGames(fixtureSlug: string): Promise { + const raw = await Get({ + endpoint: `/api/fixture/${fixtureSlug}/games`, + }) + return raw + .sort( + (a: Game, b: Game) => + new Date(a.date).getTime() - new Date(b.date).getTime() + ) + .filter((game: Game) => { + return game.awayTeam.slug !== "" && game.homeTeam.slug !== "" + }) +} diff --git a/front/src/api/player.ts b/front/src/api/player.ts index d225123..fc7b4ee 100644 --- a/front/src/api/player.ts +++ b/front/src/api/player.ts @@ -1,17 +1,17 @@ -import { GamePlayer } from "../types/game" -import Player from "../types/player" -import { Get } from "./api" - -export async function GetPlayer(slug: string): Promise { - const raw = await Get({ - endpoint: `/api/player/${slug}`, - }) - return raw -} - -export async function GetPlayerGames(slug: string): Promise { - const raw = await Get({ - endpoint: `/api/player/${slug}/games`, - }) - return raw -} +import { GamePlayer } from "../types/game" +import Player from "../types/player" +import { Get } from "./api" + +export async function GetPlayer(slug: string): Promise { + const raw = await Get({ + endpoint: `/api/player/${slug}`, + }) + return raw +} + +export async function GetPlayerGames(slug: string): Promise { + const raw = await Get({ + endpoint: `/api/player/${slug}/games`, + }) + return raw +} diff --git a/front/src/api/rankings.ts b/front/src/api/rankings.ts index 9e5cbf5..7e628d3 100644 --- a/front/src/api/rankings.ts +++ b/front/src/api/rankings.ts @@ -59,6 +59,11 @@ export interface SingleRanking { moreThan20AAPercentage: number moreThan10AAPercentage: number aaPerMin: number + duelWonPer90Min: number + effectiveClearancePer90Min: number + shotOnTargetPer90Min: number + accuratePassPer90Min: number + wonContestPer90Min: number totalMinutes: number scores: number[] player: Player diff --git a/front/src/api/team.ts b/front/src/api/team.ts new file mode 100644 index 0000000..31acd39 --- /dev/null +++ b/front/src/api/team.ts @@ -0,0 +1,36 @@ +import Player, { Membership } from "../types/player" +import Team from "../types/team" +import { Get } from "./api" + +export async function GetTeam(slug: string): Promise { + const raw = await Get({ + endpoint: `/api/team/${slug}`, + }) + return raw +} + +export interface TeamRosterResponse { + team: Team + roster: Player[] +} + +export async function GetTeamRoster(slug: string): Promise { + const raw = await Get({ + endpoint: `/api/team/${slug}/roster`, + }) + return raw +} + +export interface TeamMembershipsResponse { + team: Team + memberships: Membership[] +} + +export async function GetTeamMemberships( + slug: string +): Promise { + const raw = await Get({ + endpoint: `/api/team/${slug}/memberships`, + }) + return raw +} diff --git a/front/src/api/zone.ts b/front/src/api/zone.ts index 2397b53..759ad38 100644 --- a/front/src/api/zone.ts +++ b/front/src/api/zone.ts @@ -1,9 +1,9 @@ -import Zone from "../types/zone"; -import { Get } from "./api"; - -export async function GetAllZones(): Promise { - return await Get({ - endpoint: "/api/zone/all" - }); -} - +import Zone from "../types/zone"; +import { Get } from "./api"; + +export async function GetAllZones(): Promise { + return await Get({ + endpoint: "/api/zone/all" + }); +} + diff --git a/front/src/assets/man.png b/front/src/assets/man.png new file mode 100644 index 0000000..8408d47 Binary files /dev/null and b/front/src/assets/man.png differ diff --git a/front/src/components/badge.tsx b/front/src/components/badge.tsx new file mode 100644 index 0000000..1ec3ff6 --- /dev/null +++ b/front/src/components/badge.tsx @@ -0,0 +1,27 @@ +import { Link } from "react-router-dom" + +export default function Badge({ + imgUrl, + name, + imgHeight = "full", + link = "", +}: { + imgUrl?: string + name?: string + imgHeight?: string + link?: string +}) { + return link ? ( + +
+ + {name} +
+ + ) : ( +
+ + {name} +
+ ) +} diff --git a/front/src/components/checkbox.tsx b/front/src/components/checkbox.tsx index ec86bdf..01b0dc5 100644 --- a/front/src/components/checkbox.tsx +++ b/front/src/components/checkbox.tsx @@ -1,51 +1,51 @@ -import { ChangeEventHandler } from "react" - -interface CheckboxProps { - checked: boolean - label: string - onChange: ChangeEventHandler - id: string -} - -export default function Checkbox({ - checked, - label, - onChange, - id, -}: CheckboxProps) { - return ( -
- -
- ) -} +import { ChangeEventHandler } from "react" + +interface CheckboxProps { + checked: boolean + label: string + onChange: ChangeEventHandler + id: string +} + +export default function Checkbox({ + checked, + label, + onChange, + id, +}: CheckboxProps) { + return ( +
+ +
+ ) +} diff --git a/front/src/components/numeric_input.tsx b/front/src/components/numeric_input.tsx index c6ce32b..07b15db 100644 --- a/front/src/components/numeric_input.tsx +++ b/front/src/components/numeric_input.tsx @@ -1,33 +1,33 @@ -import InputWrapper from "./input_wrapper"; - -interface NumericInputProps { - value: number; - onChange: (value: number) => void; - label: string; - placeholder?: string; - id: string; - classname?: string; -} -export default function NumericInput({ - value, - onChange, - label, - placeholder = label, - id, - classname = "", -}: NumericInputProps) { - return ( - - { - onChange(parseInt(e.target.value)); - }} - /> - - ); -} +import InputWrapper from "./input_wrapper"; + +interface NumericInputProps { + value: number; + onChange: (value: number) => void; + label: string; + placeholder?: string; + id: string; + classname?: string; +} +export default function NumericInput({ + value, + onChange, + label, + placeholder = label, + id, + classname = "", +}: NumericInputProps) { + return ( + + { + onChange(parseInt(e.target.value)); + }} + /> + + ); +} diff --git a/front/src/components/searchBox.tsx b/front/src/components/searchBox.tsx index 7f82cae..57805e2 100644 --- a/front/src/components/searchBox.tsx +++ b/front/src/components/searchBox.tsx @@ -118,6 +118,10 @@ export function SearchBox() { onChange={handleSelect} formatGroupLabel={formatGroupLabel} formatOptionLabel={formatOptionLabel} + styles={{ + menu: (base) => ({ ...base, maxHeight: "600px" }), + menuList: (base) => ({ ...base, maxHeight: "600px" }), + }} /> ) } diff --git a/front/src/components/withdatafetching.tsx b/front/src/components/withdatafetching.tsx index 15bdbfd..e796ff4 100644 --- a/front/src/components/withdatafetching.tsx +++ b/front/src/components/withdatafetching.tsx @@ -1,35 +1,35 @@ -import { useQuery } from "@tanstack/react-query" -import ErrorBlock from "./error" -import Loader from "./loader" - -interface WithDataFetchingProps { - queryKey: string[] - queryFn: () => Promise - refetchInterval?: number - refetchOnWindowFocus?: boolean - enabled?: boolean - children: (data: T) => React.ReactNode -} - -export default function WithDataFetching({ - queryKey, - queryFn, - refetchInterval, - refetchOnWindowFocus, - enabled, - children, -}: WithDataFetchingProps) { - const { data, isLoading, isError, error } = useQuery({ - queryKey: queryKey, - queryFn: queryFn, - refetchInterval: refetchInterval, - refetchOnWindowFocus: refetchOnWindowFocus, - enabled: enabled, - }) - - if (isLoading) return - if (isError) return - if (!data) return ErrorBlock({ error: new Error("Data not found") }) - - return <>{children(data)} -} +import { useQuery } from "@tanstack/react-query" +import ErrorBlock from "./error" +import Loader from "./loader" + +interface WithDataFetchingProps { + queryKey: string[] + queryFn: () => Promise + refetchInterval?: number + refetchOnWindowFocus?: boolean + enabled?: boolean + children: (data: T) => React.ReactNode +} + +export default function WithDataFetching({ + queryKey, + queryFn, + refetchInterval, + refetchOnWindowFocus, + enabled, + children, +}: WithDataFetchingProps) { + const { data, isLoading, isError, error } = useQuery({ + queryKey: queryKey, + queryFn: queryFn, + refetchInterval: refetchInterval, + refetchOnWindowFocus: refetchOnWindowFocus, + enabled: enabled, + }) + + if (isLoading) return + if (isError) return + if (!data) return ErrorBlock({ error: new Error("Data not found") }) + + return <>{children(data)} +} diff --git a/front/src/global.css b/front/src/global.css index 64959ae..3170c79 100644 --- a/front/src/global.css +++ b/front/src/global.css @@ -1,14 +1,14 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - - -@layer components { - .btn { - @apply px-4 py-2 rounded-md text-neutral-50 text-sm font-semibold hover:shadow-md; - } -} - -body { - @apply font-sans; +@tailwind base; +@tailwind components; +@tailwind utilities; + + +@layer components { + .btn { + @apply px-4 py-2 rounded-md text-neutral-50 text-sm font-semibold hover:shadow-md; + } +} + +body { + @apply font-sans; } \ No newline at end of file diff --git a/front/src/pages/header.tsx b/front/src/pages/header.tsx index 198512d..b9f34a7 100644 --- a/front/src/pages/header.tsx +++ b/front/src/pages/header.tsx @@ -1,35 +1,35 @@ -import { Link, useLocation } from "react-router-dom" -import { SearchBox } from "../components/searchBox" - -interface HeaderLinkButtonProps { - to: string - label: string -} - -const HeaderLinkButton = ({ to, label }: HeaderLinkButtonProps) => { - const location = useLocation() - const isActive = location.pathname === to - return ( - - {label} - - ) -} - -export default function Header() { - return ( -
-
- -
-
- - - -
-
- ) -} +import { Link, useLocation } from "react-router-dom" +import { SearchBox } from "../components/searchBox" + +interface HeaderLinkButtonProps { + to: string + label: string +} + +const HeaderLinkButton = ({ to, label }: HeaderLinkButtonProps) => { + const location = useLocation() + const isActive = location.pathname === to + return ( + + {label} + + ) +} + +export default function Header() { + return ( +
+
+ +
+
+ + + +
+
+ ) +} diff --git a/front/src/pages/layout.tsx b/front/src/pages/layout.tsx index 9952b08..4e6ac48 100644 --- a/front/src/pages/layout.tsx +++ b/front/src/pages/layout.tsx @@ -1,13 +1,13 @@ -import { Outlet } from "react-router-dom" -import Header from "./header" - -export default function Layout() { - return ( - <> -
-
- -
- - ) -} +import { Outlet } from "react-router-dom" +import Header from "./header" + +export default function Layout() { + return ( + <> +
+
+ +
+ + ) +} diff --git a/front/src/pages/live/index.tsx b/front/src/pages/live/index.tsx index 58f43e5..26d1fc6 100644 --- a/front/src/pages/live/index.tsx +++ b/front/src/pages/live/index.tsx @@ -1,374 +1,374 @@ -import { useEffect, useMemo, useState } from "react" -import { GetAvailableFixtures } from "../../api/fixture" -import { GetFixtureGames } from "../../api/game" -import WithDataFetching from "../../components/withdatafetching" -import { Fixture } from "../../types/fixture" -import { Game } from "../../types/game" - -export default function Live() { - return ( - - queryKey={["availableFixtures"]} - queryFn={GetAvailableFixtures} - refetchInterval={60 * 60000} - > - {(data) => } - - ) -} - -function FixtureListLayout({ fixtures }: { fixtures: Fixture[] }) { - const [currentIndex, setCurrentIndex] = useState(() => { - const startedFixtureIndex = fixtures?.findIndex( - (fixture) => fixture.fixtureState === "started" - ) - return startedFixtureIndex !== -1 ? startedFixtureIndex : 0 - }) - - useEffect(() => { - if (currentIndex < 0) { - setCurrentIndex(0) - } else if (currentIndex >= fixtures.length) { - setCurrentIndex(fixtures.length - 1) - } - }, [currentIndex, fixtures?.length]) - - const selectedFixture = fixtures[currentIndex] - - const handlePrevious = () => { - if (currentIndex > 0) { - setCurrentIndex(currentIndex - 1) - } - } - - const handleNext = () => { - if (currentIndex < fixtures.length - 1) { - setCurrentIndex(currentIndex + 1) - } - } - return ( - <> -
-
- {currentIndex < fixtures.length - 1 ? ( - - ) : ( -
- )} -
- Gameweek - - {selectedFixture ? selectedFixture.gameWeek : ""} - -
- {currentIndex > 0 ? ( - - ) : ( -
- )} -
- {selectedFixture && ( - - queryKey={["fixtureGames", selectedFixture.slug]} - queryFn={() => GetFixtureGames(selectedFixture.slug)} - refetchInterval={10000} - enabled={!!selectedFixture} - > - {(data) => } - - )} -
- - ) -} - -function areDatesOnSameDay(date1: Date, date2: Date) { - return ( - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate() - ) -} - -type TranslationKey = "today" | "tomorrow" | "yesterday" -type LanguageCode = - | "en" - | "zh" - | "hi" - | "es" - | "ar" - | "bn" - | "pt" - | "ru" - | "ja" - | "pa" - | "jv" - | "de" - | "fr" - | "it" - | "tr" - | "ko" - | "fa" - | "uk" - | "pl" - | "ro" - | "nl" - | "th" - | "el" - | "sv" - | "cs" -type Translations = Record> -function getLocalizedWord(word: TranslationKey): string { - const translations: Translations = { - en: { today: "Today", tomorrow: "Tomorrow", yesterday: "Yesterday" }, - zh: { today: "今天", tomorrow: "明天", yesterday: "昨天" }, - hi: { today: "आज", tomorrow: "कल", yesterday: "कल" }, - es: { today: "Hoy", tomorrow: "Mañana", yesterday: "Ayer" }, - ar: { today: "اليوم", tomorrow: "غدًا", yesterday: "أمس" }, - bn: { today: "আজ", tomorrow: "আগামীকাল", yesterday: "গতকাল" }, - pt: { today: "Hoje", tomorrow: "Amanhã", yesterday: "Ontem" }, - ru: { today: "Сегодня", tomorrow: "Завтра", yesterday: "Вчера" }, - ja: { today: "今日", tomorrow: "明日", yesterday: "昨日" }, - pa: { today: "ਅੱਜ", tomorrow: "ਕਲ", yesterday: "ਕੱਲ੍ਹ" }, - jv: { today: "Dina iki", tomorrow: "Esuk", yesterday: "Wingi" }, - de: { today: "Heute", tomorrow: "Morgen", yesterday: "Gestern" }, - fr: { today: "Aujourd'hui", tomorrow: "Demain", yesterday: "Hier" }, - it: { today: "Oggi", tomorrow: "Domani", yesterday: "Ieri" }, - tr: { today: "Bugün", tomorrow: "Yarın", yesterday: "Dün" }, - ko: { today: "오늘", tomorrow: "내일", yesterday: "어제" }, - fa: { today: "امروز", tomorrow: "فردا", yesterday: "دیروز" }, - uk: { today: "Сьогодні", tomorrow: "Завтра", yesterday: "Вчора" }, - pl: { today: "Dziś", tomorrow: "Jutro", yesterday: "Wczoraj" }, - ro: { today: "Astăzi", tomorrow: "Mâine", yesterday: "Ieri" }, - nl: { today: "Vandaag", tomorrow: "Morgen", yesterday: "Gisteren" }, - th: { today: "วันนี้", tomorrow: "พรุ่งนี้", yesterday: "เมื่อวาน" }, - el: { today: "Σήμερα", tomorrow: "Αύριο", yesterday: "Χθες" }, - sv: { today: "Idag", tomorrow: "Imorgon", yesterday: "Igår" }, - cs: { today: "Dnes", tomorrow: "Zítra", yesterday: "Včera" }, - } - - const lang = navigator.language.slice(0, 2) - if ((translations as Record)[lang]) { - return translations[lang as LanguageCode][word] || word - } - return word -} - -function getDayString(day: Date) { - const today = new Date() - const todayDate = today.getDate() - const dayDate = day.getDate() - - if (areDatesOnSameDay(day, today)) { - return getLocalizedWord("today") - } - if ( - dayDate === todayDate + 1 && - day.getMonth() === today.getMonth() && - day.getFullYear() === today.getFullYear() - ) { - return getLocalizedWord("tomorrow") - } - if ( - dayDate === todayDate - 1 && - day.getMonth() === today.getMonth() && - day.getFullYear() === today.getFullYear() - ) { - return getLocalizedWord("yesterday") - } - - const dayName = new Intl.DateTimeFormat(navigator.language, { - weekday: "long", - }).format(day) - return dayName.charAt(0).toUpperCase() + dayName.slice(1) -} - -function FixtureGames({ fixture, games }: { fixture: Fixture; games: Game[] }) { - const [showOnlyStartedAndScheduled, setShowOnlyStartedAndScheduled] = - useState(false) - const toggleFilter = () => { - setShowOnlyStartedAndScheduled((prevState) => !prevState) - } - - const [selectedDay, setSelectedDay] = useState(new Date()) - useEffect(() => { - let initialDate = new Date() - if ( - initialDate > new Date(fixture.endDate) || - initialDate < new Date(fixture.startDate) - ) { - initialDate = new Date(fixture.startDate) - } - initialDate.setHours(0, 0, 0, 0) - setSelectedDay(initialDate) - }, [fixture]) - - const uniqueDays = useMemo(() => { - const daysSet = new Set() - - games?.forEach((game) => { - const d = new Date(game.date) - const dateString = `${d.getFullYear()}-${String( - d.getMonth() + 1 - ).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}` - daysSet.add(dateString) - }) - - return Array.from(daysSet).map((dateString) => new Date(dateString)) - }, [games]) - - const filteredGames = useMemo(() => { - let res = games?.filter((game: Game) => - areDatesOnSameDay(new Date(game.date), selectedDay) - ) - if (showOnlyStartedAndScheduled) { - res = res?.filter( - (game: Game) => game.status === "playing" || game.status === "scheduled" - ) - } - return res?.sort((a: Game, b: Game) => { - const time1 = new Date(a.date).getTime() - const time2 = new Date(b.date).getTime() - if (time1 === time2) { - return a.id > b.id ? 1 : -1 - } - return time1 - time2 - }) - }, [games, showOnlyStartedAndScheduled, selectedDay]) - - return ( -
-
- {uniqueDays - .sort((a: Date, b: Date) => a.getTime() - b.getTime()) - .map((day) => ( -
- -
- ))} -
-
- - -
-
- {filteredGames.map((game) => ( - <> -
- {game.status === "playing" ? ( - game.periodType === "HALF_TIME" ? ( - HT - ) : ( - {game.minutes}' - ) - ) : game.status === "played" ? ( - FT - ) : game.status === "scheduled" ? ( - - {new Date(game.date).toLocaleString(navigator.language, { - hour: "numeric", - minute: "numeric", - })} - - ) : null} -
-
- {game.homeTeam.shortName} -
- -
-
-
-
-
- {game.homePenaltyScore > 0 || game.awayPenaltyScore > 0 ? ( - ({game.homePenaltyScore}) - ) : null} - - {game.status !== "scheduled" ? game.homeGoals : "-"} - -
-
- {game.status !== "scheduled" && -} -
-
- - {game.status !== "scheduled" ? game.awayGoals : "-"} - - {game.awayPenaltyScore > 0 || game.homePenaltyScore > 0 ? ( - ({game.awayPenaltyScore}) - ) : null} -
-
-
-
-
-
- -
- {game.awayTeam.shortName} -
-
- -
-
- {game.competition.displayName} -
-
- {`${game.competition.displayName} -
-
- - ))} -
-
- ) -} +import { useEffect, useMemo, useState } from "react" +import { GetAvailableFixtures } from "../../api/fixture" +import { GetFixtureGames } from "../../api/game" +import WithDataFetching from "../../components/withdatafetching" +import { Fixture } from "../../types/fixture" +import { Game } from "../../types/game" + +export default function Live() { + return ( + + queryKey={["availableFixtures"]} + queryFn={GetAvailableFixtures} + refetchInterval={60 * 60000} + > + {(data) => } + + ) +} + +function FixtureListLayout({ fixtures }: { fixtures: Fixture[] }) { + const [currentIndex, setCurrentIndex] = useState(() => { + const startedFixtureIndex = fixtures?.findIndex( + (fixture) => fixture.fixtureState === "started" + ) + return startedFixtureIndex !== -1 ? startedFixtureIndex : 0 + }) + + useEffect(() => { + if (currentIndex < 0) { + setCurrentIndex(0) + } else if (currentIndex >= fixtures.length) { + setCurrentIndex(fixtures.length - 1) + } + }, [currentIndex, fixtures?.length]) + + const selectedFixture = fixtures[currentIndex] + + const handlePrevious = () => { + if (currentIndex > 0) { + setCurrentIndex(currentIndex - 1) + } + } + + const handleNext = () => { + if (currentIndex < fixtures.length - 1) { + setCurrentIndex(currentIndex + 1) + } + } + return ( + <> +
+
+ {currentIndex < fixtures.length - 1 ? ( + + ) : ( +
+ )} +
+ Gameweek + + {selectedFixture ? selectedFixture.gameWeek : ""} + +
+ {currentIndex > 0 ? ( + + ) : ( +
+ )} +
+ {selectedFixture && ( + + queryKey={["fixtureGames", selectedFixture.slug]} + queryFn={() => GetFixtureGames(selectedFixture.slug)} + refetchInterval={10000} + enabled={!!selectedFixture} + > + {(data) => } + + )} +
+ + ) +} + +function areDatesOnSameDay(date1: Date, date2: Date) { + return ( + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate() + ) +} + +type TranslationKey = "today" | "tomorrow" | "yesterday" +type LanguageCode = + | "en" + | "zh" + | "hi" + | "es" + | "ar" + | "bn" + | "pt" + | "ru" + | "ja" + | "pa" + | "jv" + | "de" + | "fr" + | "it" + | "tr" + | "ko" + | "fa" + | "uk" + | "pl" + | "ro" + | "nl" + | "th" + | "el" + | "sv" + | "cs" +type Translations = Record> +function getLocalizedWord(word: TranslationKey): string { + const translations: Translations = { + en: { today: "Today", tomorrow: "Tomorrow", yesterday: "Yesterday" }, + zh: { today: "今天", tomorrow: "明天", yesterday: "昨天" }, + hi: { today: "आज", tomorrow: "कल", yesterday: "कल" }, + es: { today: "Hoy", tomorrow: "Mañana", yesterday: "Ayer" }, + ar: { today: "اليوم", tomorrow: "غدًا", yesterday: "أمس" }, + bn: { today: "আজ", tomorrow: "আগামীকাল", yesterday: "গতকাল" }, + pt: { today: "Hoje", tomorrow: "Amanhã", yesterday: "Ontem" }, + ru: { today: "Сегодня", tomorrow: "Завтра", yesterday: "Вчера" }, + ja: { today: "今日", tomorrow: "明日", yesterday: "昨日" }, + pa: { today: "ਅੱਜ", tomorrow: "ਕਲ", yesterday: "ਕੱਲ੍ਹ" }, + jv: { today: "Dina iki", tomorrow: "Esuk", yesterday: "Wingi" }, + de: { today: "Heute", tomorrow: "Morgen", yesterday: "Gestern" }, + fr: { today: "Aujourd'hui", tomorrow: "Demain", yesterday: "Hier" }, + it: { today: "Oggi", tomorrow: "Domani", yesterday: "Ieri" }, + tr: { today: "Bugün", tomorrow: "Yarın", yesterday: "Dün" }, + ko: { today: "오늘", tomorrow: "내일", yesterday: "어제" }, + fa: { today: "امروز", tomorrow: "فردا", yesterday: "دیروز" }, + uk: { today: "Сьогодні", tomorrow: "Завтра", yesterday: "Вчора" }, + pl: { today: "Dziś", tomorrow: "Jutro", yesterday: "Wczoraj" }, + ro: { today: "Astăzi", tomorrow: "Mâine", yesterday: "Ieri" }, + nl: { today: "Vandaag", tomorrow: "Morgen", yesterday: "Gisteren" }, + th: { today: "วันนี้", tomorrow: "พรุ่งนี้", yesterday: "เมื่อวาน" }, + el: { today: "Σήμερα", tomorrow: "Αύριο", yesterday: "Χθες" }, + sv: { today: "Idag", tomorrow: "Imorgon", yesterday: "Igår" }, + cs: { today: "Dnes", tomorrow: "Zítra", yesterday: "Včera" }, + } + + const lang = navigator.language.slice(0, 2) + if ((translations as Record)[lang]) { + return translations[lang as LanguageCode][word] || word + } + return word +} + +function getDayString(day: Date) { + const today = new Date() + const todayDate = today.getDate() + const dayDate = day.getDate() + + if (areDatesOnSameDay(day, today)) { + return getLocalizedWord("today") + } + if ( + dayDate === todayDate + 1 && + day.getMonth() === today.getMonth() && + day.getFullYear() === today.getFullYear() + ) { + return getLocalizedWord("tomorrow") + } + if ( + dayDate === todayDate - 1 && + day.getMonth() === today.getMonth() && + day.getFullYear() === today.getFullYear() + ) { + return getLocalizedWord("yesterday") + } + + const dayName = new Intl.DateTimeFormat(navigator.language, { + weekday: "long", + }).format(day) + return dayName.charAt(0).toUpperCase() + dayName.slice(1) +} + +function FixtureGames({ fixture, games }: { fixture: Fixture; games: Game[] }) { + const [showOnlyStartedAndScheduled, setShowOnlyStartedAndScheduled] = + useState(false) + const toggleFilter = () => { + setShowOnlyStartedAndScheduled((prevState) => !prevState) + } + + const [selectedDay, setSelectedDay] = useState(new Date()) + useEffect(() => { + let initialDate = new Date() + if ( + initialDate > new Date(fixture.endDate) || + initialDate < new Date(fixture.startDate) + ) { + initialDate = new Date(fixture.startDate) + } + initialDate.setHours(0, 0, 0, 0) + setSelectedDay(initialDate) + }, [fixture]) + + const uniqueDays = useMemo(() => { + const daysSet = new Set() + + games?.forEach((game) => { + const d = new Date(game.date) + const dateString = `${d.getFullYear()}-${String( + d.getMonth() + 1 + ).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}` + daysSet.add(dateString) + }) + + return Array.from(daysSet).map((dateString) => new Date(dateString)) + }, [games]) + + const filteredGames = useMemo(() => { + let res = games?.filter((game: Game) => + areDatesOnSameDay(new Date(game.date), selectedDay) + ) + if (showOnlyStartedAndScheduled) { + res = res?.filter( + (game: Game) => game.status === "playing" || game.status === "scheduled" + ) + } + return res?.sort((a: Game, b: Game) => { + const time1 = new Date(a.date).getTime() + const time2 = new Date(b.date).getTime() + if (time1 === time2) { + return a.id > b.id ? 1 : -1 + } + return time1 - time2 + }) + }, [games, showOnlyStartedAndScheduled, selectedDay]) + + return ( +
+
+ {uniqueDays + .sort((a: Date, b: Date) => a.getTime() - b.getTime()) + .map((day) => ( +
+ +
+ ))} +
+
+ + +
+
+ {filteredGames.map((game) => ( + <> +
+ {game.status === "playing" ? ( + game.periodType === "HALF_TIME" ? ( + HT + ) : ( + {game.minutes}' + ) + ) : game.status === "played" ? ( + FT + ) : game.status === "scheduled" ? ( + + {new Date(game.date).toLocaleString(navigator.language, { + hour: "numeric", + minute: "numeric", + })} + + ) : null} +
+
+ {game.homeTeam.shortName} +
+ +
+
+
+
+
+ {game.homePenaltyScore > 0 || game.awayPenaltyScore > 0 ? ( + ({game.homePenaltyScore}) + ) : null} + + {game.status !== "scheduled" ? game.homeGoals : "-"} + +
+
+ {game.status !== "scheduled" && -} +
+
+ + {game.status !== "scheduled" ? game.awayGoals : "-"} + + {game.awayPenaltyScore > 0 || game.homePenaltyScore > 0 ? ( + ({game.awayPenaltyScore}) + ) : null} +
+
+
+
+
+
+ +
+ {game.awayTeam.shortName} +
+
+ +
+
+ {game.competition.displayName} +
+
+ {`${game.competition.displayName} +
+
+ + ))} +
+
+ ) +} diff --git a/front/src/pages/player/club_history.tsx b/front/src/pages/player/club_history.tsx index ed5d09e..06afa7a 100644 --- a/front/src/pages/player/club_history.tsx +++ b/front/src/pages/player/club_history.tsx @@ -1,79 +1,104 @@ -import { useMemo } from "react" -import Player from "../../types/player" - -export default function ClubHistory({ player }: { player: Player }) { - const clubMemberships = useMemo( - () => - player.clubMembership - .filter((membership) => membership.membershipType === "club") - .sort((a, b) => { - if (!a.endDate) return -1 - if (!b.endDate) return 1 - if (new Date(a.endDate).getTime() === new Date(b.endDate).getTime()) { - return ( - new Date(b.startDate).getTime() - new Date(a.startDate).getTime() - ) - } - return new Date(b.endDate).getTime() - new Date(a.endDate).getTime() - }), - [player.clubMembership] - ) - return ( -
- {clubMemberships.map((membership) => ( - <> -
- -
-
- -
- -
{membership.team?.displayName}
-
- - {new Date(membership.startDate).toLocaleDateString(undefined, { - month: "numeric", - year: "numeric", - })} - - {membership.endDate ? "->" : ""} - - {membership.endDate - ? new Date(membership.endDate).toLocaleDateString(undefined, { - month: "numeric", - year: "numeric", - }) - : ""} - -
-
- {(() => { - const endDate = membership.endDate - ? new Date(membership.endDate) - : new Date() - const startDate = new Date(membership.startDate) - const diffDays = Math.floor( - (endDate.getTime() - startDate.getTime()) / - (1000 * 60 * 60 * 24) - ) - const diffYears = Math.floor(diffDays / 365) - const remainingDays = diffDays % 365 - if (diffYears > 1) { - return `${diffYears} years ${remainingDays} days` - } else if (diffYears === 1) { - return `1 year ${remainingDays} days` - } else { - return `${diffDays} days` - } - })()} -
- - ))} -
- ) -} +import { useMemo } from "react" +import { Link, useParams } from "react-router-dom" +import { GetPlayer } from "../../api/player" +import WithDataFetching from "../../components/withdatafetching" +import Player from "../../types/player" + +export default function ClubHistory() { + const { slug } = useParams() + if (!slug) { + return
No
+ } + return ( + GetPlayer(slug)} + > + {(player: Player) => } + + ) +} + +function ClubHistoryLayout({ player }: { player: Player }) { + const clubMemberships = useMemo( + () => + player.clubMembership + .filter((membership) => membership.membershipType === "club") + .sort((a, b) => { + if (!a.endDate && !b.endDate) { + return ( + new Date(b.startDate).getTime() - new Date(a.startDate).getTime() + ) + } + if (!a.endDate) return -1 + if (!b.endDate) return 1 + if (new Date(a.endDate).getTime() === new Date(b.endDate).getTime()) { + return ( + new Date(b.startDate).getTime() - new Date(a.startDate).getTime() + ) + } + return new Date(b.endDate).getTime() - new Date(a.endDate).getTime() + }), + [player.clubMembership] + ) + return ( +
+ {clubMemberships.map((membership) => ( + <> +
+ +
+ +
+ +
+ + +
{membership.team?.displayName}
+
+ + {new Date(membership.startDate).toLocaleDateString(undefined, { + month: "numeric", + year: "numeric", + })} + + {membership.endDate ? "->" : ""} + + {membership.endDate + ? new Date(membership.endDate).toLocaleDateString(undefined, { + month: "numeric", + year: "numeric", + }) + : ""} + +
+
+ {(() => { + const endDate = membership.endDate + ? new Date(membership.endDate) + : new Date() + const startDate = new Date(membership.startDate) + const diffDays = Math.floor( + (endDate.getTime() - startDate.getTime()) / + (1000 * 60 * 60 * 24) + ) + const diffYears = Math.floor(diffDays / 365) + const remainingDays = diffDays % 365 + if (diffYears > 1) { + return `${diffYears} years ${remainingDays} days` + } else if (diffYears === 1) { + return `1 year ${remainingDays} days` + } else { + return `${diffDays} days` + } + })()} +
+ + ))} +
+ ) +} diff --git a/front/src/pages/player/components/card_icon.tsx b/front/src/pages/player/components/card_icon.tsx index 735a428..416215b 100644 --- a/front/src/pages/player/components/card_icon.tsx +++ b/front/src/pages/player/components/card_icon.tsx @@ -1,25 +1,25 @@ -import React from "react" - -interface CardIconProps { - size?: number - color?: string -} - -const CardIcon: React.FC = ({ - size = 16, - color = "currentColor", -}) => { - return ( - - - - ) -} - -export default CardIcon +import React from "react" + +interface CardIconProps { + size?: number + color?: string +} + +const CardIcon: React.FC = ({ + size = 16, + color = "currentColor", +}) => { + return ( + + + + ) +} + +export default CardIcon diff --git a/front/src/pages/player/components/days_selection.tsx b/front/src/pages/player/components/days_selection.tsx index 7b8cf57..682d25b 100644 --- a/front/src/pages/player/components/days_selection.tsx +++ b/front/src/pages/player/components/days_selection.tsx @@ -1,117 +1,117 @@ -import { useState } from "react" - -function extractDateWithOffset(date: Date, offsetDays: number) { - const year = date.getFullYear() - const month = date.getMonth() - const day = date.getDate() - - const resultDate = new Date(year, month, day) - resultDate.setDate(resultDate.getDate() + offsetDays) - - return resultDate -} - -const CustomRadioButton = ({ - value, - currentValue, - onClick, - label, -}: { - value: string - currentValue: string - onClick: (value: string) => void - label: string -}) => { - const isActive = value === currentValue - const bg = isActive ? "bg-primary-blue-500" : "bg-neutral-700" - const hoverBg = isActive - ? "hover:bg-primary-blue-600" - : "hover:bg-neutral-600" - const activeBg = isActive - ? "active:bg-primary-blue-700" - : "active:bg-neutral-500" - - return ( - - ) -} - -function DaysSelector({ - days, - setDays, -}: { - days: number - setDays: (days: number) => void -}) { - return ( -
- setDays(Number(value))} - label="3 days" - /> - setDays(Number(value))} - label="1 week" - /> - setDays(Number(value))} - label="1 month" - /> - setDays(Number(value))} - label="3 months" - /> - setDays(Number(value))} - label="6 months" - /> - setDays(Number(value))} - label="1 year" - /> - setDays(Number(value))} - label="All" - /> -
- ) -} - -export default function DaysSelectionWrapper({ - children, -}: { - children: (startDate: Date, endDate: Date) => React.ReactNode -}) { - const [days, setDays] = useState(365) - const farEnoughDate = new Date(2000, 0, 1) // You can set this to an earlier date if needed - const startDate = - days === 0 ? farEnoughDate : extractDateWithOffset(new Date(), -days) - const endDate = extractDateWithOffset(new Date(), 1) - - return ( -
- -
{children(startDate, endDate)}
-
- ) -} +import { useState } from "react" + +function extractDateWithOffset(date: Date, offsetDays: number) { + const year = date.getFullYear() + const month = date.getMonth() + const day = date.getDate() + + const resultDate = new Date(year, month, day) + resultDate.setDate(resultDate.getDate() + offsetDays) + + return resultDate +} + +const CustomRadioButton = ({ + value, + currentValue, + onClick, + label, +}: { + value: string + currentValue: string + onClick: (value: string) => void + label: string +}) => { + const isActive = value === currentValue + const bg = isActive ? "bg-primary-blue-500" : "bg-neutral-700" + const hoverBg = isActive + ? "hover:bg-primary-blue-600" + : "hover:bg-neutral-600" + const activeBg = isActive + ? "active:bg-primary-blue-700" + : "active:bg-neutral-500" + + return ( + + ) +} + +function DaysSelector({ + days, + setDays, +}: { + days: number + setDays: (days: number) => void +}) { + return ( +
+ setDays(Number(value))} + label="3 days" + /> + setDays(Number(value))} + label="1 week" + /> + setDays(Number(value))} + label="1 month" + /> + setDays(Number(value))} + label="3 months" + /> + setDays(Number(value))} + label="6 months" + /> + setDays(Number(value))} + label="1 year" + /> + setDays(Number(value))} + label="All" + /> +
+ ) +} + +export default function DaysSelectionWrapper({ + children, +}: { + children: (startDate: Date, endDate: Date) => React.ReactNode +}) { + const [days, setDays] = useState(365) + const farEnoughDate = new Date(2000, 0, 1) // You can set this to an earlier date if needed + const startDate = + days === 0 ? farEnoughDate : extractDateWithOffset(new Date(), -days) + const endDate = extractDateWithOffset(new Date(), 1) + + return ( +
+ +
{children(startDate, endDate)}
+
+ ) +} diff --git a/front/src/pages/player/components/filter_select.tsx b/front/src/pages/player/components/filter_select.tsx index 490bdd7..bee7d8b 100644 --- a/front/src/pages/player/components/filter_select.tsx +++ b/front/src/pages/player/components/filter_select.tsx @@ -1,65 +1,65 @@ -import Select, { MultiValue, SingleValue } from "react-select" - -interface FilterOption { - value: T - label: string -} - -interface FilterSelectProps { - value: T - options: FilterOption[] - onChange: (value: T) => void - isSearchable?: boolean -} - -export function FilterSelect({ - value, - options, - onChange, - isSearchable = false, -}: FilterSelectProps) { - return ( - ) => - values.includes(option.value) - )} - isMulti - isSearchable={isSearchable} - options={options} - placeholder={placeHolder} - isDisabled={options.length === 0} - onChange={(options: MultiValue>) => { - onChange(options.map((option: FilterOption) => option.value) as T[]) - }} - /> - ) -} +import Select, { MultiValue, SingleValue } from "react-select" + +interface FilterOption { + value: T + label: string +} + +interface FilterSelectProps { + value: T + options: FilterOption[] + onChange: (value: T) => void + isSearchable?: boolean +} + +export function FilterSelect({ + value, + options, + onChange, + isSearchable = false, +}: FilterSelectProps) { + return ( + ) => + values.includes(option.value) + )} + isMulti + isSearchable={isSearchable} + options={options} + placeholder={placeHolder} + isDisabled={options.length === 0} + onChange={(options: MultiValue>) => { + onChange(options.map((option: FilterOption) => option.value) as T[]) + }} + /> + ) +} diff --git a/front/src/pages/player/components/scorebox.tsx b/front/src/pages/player/components/scorebox.tsx index 9055afd..b5e334e 100644 --- a/front/src/pages/player/components/scorebox.tsx +++ b/front/src/pages/player/components/scorebox.tsx @@ -1,23 +1,23 @@ -import { GetScoreColor } from "../../../types/game" - -export default function ScoreBox({ - score, - colorfunc, -}: { - score: number - colorfunc?: (score: number, tailwind: boolean) => string -}) { - if (!colorfunc) colorfunc = GetScoreColor - return ( -
- - {Number(score).toFixed(0)} - -
- ) -} +import { GetScoreColor } from "../../../types/game" + +export default function ScoreBox({ + score, + colorfunc, +}: { + score: number + colorfunc?: (score: number, tailwind: boolean) => string +}) { + if (!colorfunc) colorfunc = GetScoreColor + return ( +
+ + {Number(score).toFixed(0)} + +
+ ) +} diff --git a/front/src/pages/player/index.tsx b/front/src/pages/player/index.tsx index 6995528..38459f7 100644 --- a/front/src/pages/player/index.tsx +++ b/front/src/pages/player/index.tsx @@ -1,56 +1,53 @@ -import { useState } from "react" -import { useParams } from "react-router" -import { GetPlayer } from "../../api/player" -import WithDataFetching from "../../components/withdatafetching" -import Player from "../../types/player" -import ClubHistory from "./club_history" -import Scores from "./scores" -import Sidebar from "./sidebar" - -export default function PlayerPage() { - const { slug } = useParams() - - if (!slug) { - return
No slug
- } - return ( - GetPlayer(slug)} - > - {(player) => } - - ) - - function PlayerLayout({ player }: { player: Player }) { - const [activeTab, setActiveTab] = useState("club_history") - const tabs = [ - { id: "club_history", title: "Club History" }, - { id: "scores", title: "Scores" }, - ] - return ( -
- -
-
- {tabs.map((tab) => ( - - ))} -
-
- {activeTab === "club_history" && ( - - )} - {activeTab === "scores" && } -
-
-
- ) - } -} +import { Outlet, useLocation, useParams } from "react-router" +import { Link } from "react-router-dom" +import { GetPlayer } from "../../api/player" +import WithDataFetching from "../../components/withdatafetching" +import Player from "../../types/player" +import Sidebar from "./sidebar" + +export default function PlayerPage() { + const { slug } = useParams() + + if (!slug) { + return
No slug
+ } + return ( + GetPlayer(slug)} + > + {(player) => } + + ) + + function PlayerLayout({ player }: { player: Player }) { + const { pathname } = useLocation() + const tabs = [ + { id: "summary", title: "Summary" }, + { id: "clubhistory", title: "Club History" }, + { id: "scores", title: "Scores" }, + ] + return ( +
+ +
+
+ {tabs.map((tab) => ( + +
+ {tab.title} +
+ + ))} +
+
+ +
+
+
+ ) + } +} diff --git a/front/src/pages/player/routes.tsx b/front/src/pages/player/routes.tsx new file mode 100644 index 0000000..b8784f0 --- /dev/null +++ b/front/src/pages/player/routes.tsx @@ -0,0 +1,27 @@ +import PlayerPage from "." +import ClubHistory from "./club_history" +import Scores from "./scores" +import Summary from "./summary" + +export const playerRoutes = { + path: "player/:slug", + element: , + children: [ + { + path: "", + element: , + }, + { + path: "summary", + element: , + }, + { + path: "clubhistory", + element: , + }, + { + path: "scores", + element: , + }, + ], +} diff --git a/front/src/pages/player/score_graph.tsx b/front/src/pages/player/score_graph.tsx index 8a14f6a..98ce4b2 100644 --- a/front/src/pages/player/score_graph.tsx +++ b/front/src/pages/player/score_graph.tsx @@ -1,184 +1,182 @@ -import { - addMonths, - format, - isSameDay, - startOfMonth, - startOfYear, - subMonths, -} from "date-fns" -import moment from "moment/moment" -import { useMemo } from "react" -import { - Cell, - ComposedChart, - LabelList, - ReferenceLine, - ResponsiveContainer, - Scatter, - Tooltip, - XAxis, - YAxis, -} from "recharts" -import { - Game, - GamePlayer, - GamePlayerScore, - GetScoreColor, -} from "../../types/game" -import Player from "../../types/player" - -type DataPoint = { - date: Date - matchScore?: number - decisiveScore?: number - aaScore?: number - game?: Game - score?: GamePlayerScore - gameweek: number - dateAsNumber: number -} - -const CustomTooltip = ({ active, payload }: any) => { - if (active && payload && payload.length) { - const dataPoint = payload[0].payload - - const { gameweek, date } = dataPoint - const formattedDate = moment(date).format("DD MMM YYYY") - - return ( -
-

{`Date: ${formattedDate}`}

- {gameweek &&

{`Gameweek: ${gameweek}`}

} - {payload.map((item: any) => ( -

{`${item.name}: ${Math.round(item.value)}`}

- ))} -
- ) - } - - return null -} - -export default function ScoreGraph({ - scores, - startDate, - endDate, -}: { - player: Player - scores: GamePlayer[] - startDate: Date - endDate: Date -}) { - const CustomXAxisTick = ({ x, y, payload }: any) => { - const date = new Date(payload.value) - const isFirstJan = isSameDay(date, startOfYear(date)) - const isFirstOfMonth = isSameDay(date, startOfMonth(date)) - const displayText = isFirstJan - ? format(date, "yyyy") - : isFirstOfMonth - ? format(date, "MMM") - : "" - - return ( - - - {displayText} - - - ) - } - - const adjustedStartDate = useMemo(() => { - const earliestScoreDate = scores.reduce((minDate, score) => { - const scoreDate = new Date(score.game?.date ?? "") - return minDate > scoreDate ? scoreDate : minDate - }, new Date()) - return startDate > earliestScoreDate ? startDate : earliestScoreDate - }, [scores, startDate]) - - const filteredScores = useMemo( - () => - scores - .filter( - (score) => - new Date(score.game?.date ?? "") >= startDate && - new Date(score.game?.date ?? "") <= endDate - ) - .map((score) => ({ - date: score.game?.fixture.startDate ?? new Date(), - matchScore: score.score?.score, - decisiveScore: score.score?.decisiveScore, - aaScore: score.score?.allAroundScore, - game: score.game, - score: score.score, - gameweek: score.game?.fixture.gameWeek ?? 0, - dateAsNumber: - new Date(score.game?.fixture.startDate ?? new Date()).getTime() ?? - 0, - })) - .sort((a, b) => a.gameweek - b.gameweek), - [scores, startDate, endDate] - ) - - const ticks = useMemo(() => { - const start = startOfMonth(adjustedStartDate) - const end = startOfMonth(endDate) - let current = start - const dates = [] - while (current <= end) { - dates.push(current.getTime()) - current = addMonths(current, 1) - } - dates.unshift(subMonths(start, 1).getTime()) - dates.push(addMonths(end, 1).getTime()) - return dates - }, [adjustedStartDate, endDate]) - - return ( -
- - - } - ticks={ticks} - /> - - - {filteredScores?.map((entry, index) => ( - - ))} - Math.round(value)} - /> - - {[0, 20, 40, 60, 80, 100].map((value) => ( - - ))} - - } /> - - -
- ) -} +import { + addMonths, + format, + isSameDay, + startOfMonth, + startOfYear, + subMonths, +} from "date-fns" +import moment from "moment/moment" +import { useMemo } from "react" +import { + Cell, + ComposedChart, + LabelList, + ReferenceLine, + ResponsiveContainer, + Scatter, + Tooltip, + XAxis, + YAxis, +} from "recharts" +import { + Game, + GamePlayer, + GamePlayerScore, + GetScoreColor, +} from "../../types/game" + +type DataPoint = { + date: Date + matchScore?: number + decisiveScore?: number + aaScore?: number + game?: Game + score?: GamePlayerScore + gameweek: number + dateAsNumber: number +} + +const CustomTooltip = ({ active, payload }: any) => { + if (active && payload && payload.length) { + const dataPoint = payload[0].payload + + const { gameweek, date } = dataPoint + const formattedDate = moment(date).format("DD MMM YYYY") + + return ( +
+

{`Date: ${formattedDate}`}

+ {gameweek &&

{`Gameweek: ${gameweek}`}

} + {payload.map((item: any) => ( +

{`${item.name}: ${Math.round(item.value)}`}

+ ))} +
+ ) + } + + return null +} + +export default function ScoreGraph({ + scores, + startDate, + endDate, +}: { + scores: GamePlayer[] + startDate: Date + endDate: Date +}) { + const CustomXAxisTick = ({ x, y, payload }: any) => { + const date = new Date(payload.value) + const isFirstJan = isSameDay(date, startOfYear(date)) + const isFirstOfMonth = isSameDay(date, startOfMonth(date)) + const displayText = isFirstJan + ? format(date, "yyyy") + : isFirstOfMonth + ? format(date, "MMM") + : "" + + return ( + + + {displayText} + + + ) + } + + const adjustedStartDate = useMemo(() => { + const earliestScoreDate = scores.reduce((minDate, score) => { + const scoreDate = new Date(score.game?.date ?? "") + return minDate > scoreDate ? scoreDate : minDate + }, new Date()) + return startDate > earliestScoreDate ? startDate : earliestScoreDate + }, [scores, startDate]) + + const filteredScores = useMemo( + () => + scores + .filter( + (score) => + new Date(score.game?.date ?? "") >= startDate && + new Date(score.game?.date ?? "") <= endDate + ) + .map((score) => ({ + date: score.game?.fixture.startDate ?? new Date(), + matchScore: score.score?.score, + decisiveScore: score.score?.decisiveScore, + aaScore: score.score?.allAroundScore, + game: score.game, + score: score.score, + gameweek: score.game?.fixture.gameWeek ?? 0, + dateAsNumber: + new Date(score.game?.fixture.startDate ?? new Date()).getTime() ?? + 0, + })) + .sort((a, b) => a.gameweek - b.gameweek), + [scores, startDate, endDate] + ) + + const ticks = useMemo(() => { + const start = startOfMonth(adjustedStartDate) + const end = startOfMonth(endDate) + let current = start + const dates = [] + while (current <= end) { + dates.push(current.getTime()) + current = addMonths(current, 1) + } + dates.unshift(subMonths(start, 1).getTime()) + dates.push(addMonths(end, 1).getTime()) + return dates + }, [adjustedStartDate, endDate]) + + return ( +
+ + + } + ticks={ticks} + /> + + + {filteredScores?.map((entry, index) => ( + + ))} + Math.round(value)} + /> + + {[0, 20, 40, 60, 80, 100].map((value) => ( + + ))} + + } /> + + +
+ ) +} diff --git a/front/src/pages/player/score_table.tsx b/front/src/pages/player/score_table.tsx index 424181c..ee848c6 100644 --- a/front/src/pages/player/score_table.tsx +++ b/front/src/pages/player/score_table.tsx @@ -1,363 +1,363 @@ -import moment from "moment" -import React from "react" -import { BiFootball } from "react-icons/bi" -import { BsHandThumbsUp } from "react-icons/bs" -import { FaHandSparkles } from "react-icons/fa" -import { GiFist, GiFootprint, GiLeg, GiWhistle } from "react-icons/gi" -import { TbHandStop } from "react-icons/tb" -import { - GamePlayer, - GetAAScoreColor, - GetDSScoreColor, - GetMinutesScoreColor, - GetScoreColor, -} from "../../types/game" -import Player from "../../types/player" -import CardIcon from "./components/card_icon" -import { FilterSelect } from "./components/filter_select" -import ScoreBox from "./components/scorebox" - -type GameEventProps = { - count: number | undefined - icon: React.ElementType - positive?: boolean - label?: string -} - -function GameEvent({ - count, - icon: Icon, - positive = true, - label = "", -}: GameEventProps) { - if (!count || count === 0) { - return null - } - - return ( -
- {count} -
- -
-
- ) -} - -function ScoreText({ score }: { score: number | string }) { - return ( - - {score} - - ) -} - -function ScoreHeader({ label, tooltip }: { label: string; tooltip: string }) { - return ( - - {label} - - ) -} - -export default function ScoreTable({ - player, - scores, -}: { - player: Player - scores: GamePlayer[] -}) { - const [statCategory, setStatCategory] = React.useState("def") - - let availableStatCategory = [ - { value: "def", label: "Defensive" }, - { value: "off", label: "Offensive" }, - { value: "poss", label: "Possession" }, - ] - if (player.fieldPosition === "Goalkeeper") { - availableStatCategory = [ - ...availableStatCategory, - { value: "goal", label: "Goalkeeping" }, - ] - } - - return ( -
-
- -
- - - - - - - - - - - - - - - - - {statCategory === "def" && ( - <> - - - - - - - - - )} - {statCategory === "off" && ( - <> - - - - - - - - - )} - {statCategory === "poss" && ( - <> - - - - - - - - - - - )} - {statCategory === "goal" && ( - <> - - - - - - - - - - - )} - - - - {scores - ?.sort((a, b) => - new Date(a.game?.date ?? new Date()) < - new Date(b.game?.date ?? new Date()) - ? 1 - : -1 - ) - .map((scoreData) => { - if (!scoreData.game || !scoreData.score) return null - return ( - - - - - - - - - - - - - - - {statCategory === "def" && - (scoreData.score.minutesPlayed ?? 0) > 0 && ( - <> - - - - - - - - - )} - {statCategory === "off" && - scoreData.score.minutesPlayed > 0 && ( - <> - - - - - - - - - )} - {statCategory === "poss" && - scoreData.score.minutesPlayed > 0 && ( - <> - - - - - - - - - - - )} - {statCategory === "goal" && - scoreData.score.minutesPlayed > 0 && ( - <> - - - - - - - - - - - )} - - ) - })} - -
- GW - Date - Minutes - HomeAwayScore - DS - - AA -
{scoreData.game.fixture?.gameWeek}{moment(scoreData.game.date).format("DD MMMM YYYY")} - - {scoreData.score.minutesPlayed} - - - - {scoreData.game.homeTeam.displayName} - - {scoreData.game.homeGoals}-{scoreData.game.awayGoals} - - {scoreData.game.awayTeam.displayName} - - - - -
- - - - - - - - } - label="Red card" - /> - } - label="Yellow card" - /> -
-
- - - - - {scoreData.score.cleanSheet ? : ""} -
-
- ) -} +import moment from "moment" +import React from "react" +import { BiFootball } from "react-icons/bi" +import { BsHandThumbsUp } from "react-icons/bs" +import { FaHandSparkles } from "react-icons/fa" +import { GiFist, GiFootprint, GiLeg, GiWhistle } from "react-icons/gi" +import { TbHandStop } from "react-icons/tb" +import { + GamePlayer, + GetAAScoreColor, + GetDSScoreColor, + GetMinutesScoreColor, + GetScoreColor, +} from "../../types/game" +import Player from "../../types/player" +import CardIcon from "./components/card_icon" +import { FilterSelect } from "./components/filter_select" +import ScoreBox from "./components/scorebox" + +type GameEventProps = { + count: number | undefined + icon: React.ElementType + positive?: boolean + label?: string +} + +function GameEvent({ + count, + icon: Icon, + positive = true, + label = "", +}: GameEventProps) { + if (!count || count === 0) { + return null + } + + return ( +
+ {count} +
+ +
+
+ ) +} + +function ScoreText({ score }: { score: number | string }) { + return ( + + {score} + + ) +} + +function ScoreHeader({ label, tooltip }: { label: string; tooltip: string }) { + return ( + + {label} + + ) +} + +export default function ScoreTable({ + player, + scores, +}: { + player: Player + scores: GamePlayer[] +}) { + const [statCategory, setStatCategory] = React.useState("def") + + let availableStatCategory = [ + { value: "def", label: "Defensive" }, + { value: "off", label: "Offensive" }, + { value: "poss", label: "Possession" }, + ] + if (player.fieldPosition === "Goalkeeper") { + availableStatCategory = [ + ...availableStatCategory, + { value: "goal", label: "Goalkeeping" }, + ] + } + + return ( +
+
+ +
+ + + + + + + + + + + + + + + + + {statCategory === "def" && ( + <> + + + + + + + + + )} + {statCategory === "off" && ( + <> + + + + + + + + + )} + {statCategory === "poss" && ( + <> + + + + + + + + + + + )} + {statCategory === "goal" && ( + <> + + + + + + + + + + + )} + + + + {scores + ?.sort((a, b) => + new Date(a.game?.date ?? new Date()) < + new Date(b.game?.date ?? new Date()) + ? 1 + : -1 + ) + .map((scoreData) => { + if (!scoreData.game || !scoreData.score) return null + return ( + + + + + + + + + + + + + + + {statCategory === "def" && + (scoreData.score.minutesPlayed ?? 0) > 0 && ( + <> + + + + + + + + + )} + {statCategory === "off" && + scoreData.score.minutesPlayed > 0 && ( + <> + + + + + + + + + )} + {statCategory === "poss" && + scoreData.score.minutesPlayed > 0 && ( + <> + + + + + + + + + + + )} + {statCategory === "goal" && + scoreData.score.minutesPlayed > 0 && ( + <> + + + + + + + + + + + )} + + ) + })} + +
+ GW + Date + Minutes + HomeAwayScore + DS + + AA +
{scoreData.game.fixture?.gameWeek}{moment(scoreData.game.date).format("DD MMMM YYYY")} + + {scoreData.score.minutesPlayed} + + + + {scoreData.game.homeTeam.displayName} + + {scoreData.game.homeGoals}-{scoreData.game.awayGoals} + + {scoreData.game.awayTeam.displayName} + + + + +
+ + + + + + + + } + label="Red card" + /> + } + label="Yellow card" + /> +
+
+ + + + + {scoreData.score.cleanSheet ? : ""} +
+
+ ) +} diff --git a/front/src/pages/player/scores.tsx b/front/src/pages/player/scores.tsx index 9249bb8..fc117eb 100644 --- a/front/src/pages/player/scores.tsx +++ b/front/src/pages/player/scores.tsx @@ -1,36 +1,47 @@ -import { GetPlayerGames } from "../../api/player" -import WithDataFetching from "../../components/withdatafetching" -import Player from "../../types/player" -import DaysSelectionWrapper from "./components/days_selection" -import ScoreGraph from "./score_graph" -import ScoreTable from "./score_table" - -export default function Scores({ player }: { player: Player }) { - return ( - - {(startDate, endDate) => { - return ( - GetPlayerGames(player.slug)} - refetchOnWindowFocus={false} - > - {(scores) => { - return ( -
- - -
- ) - }} -
- ) - }} -
- ) -} +import { useParams } from "react-router" +import { GetPlayer, GetPlayerGames } from "../../api/player" +import WithDataFetching from "../../components/withdatafetching" +import Player from "../../types/player" +import DaysSelectionWrapper from "./components/days_selection" +import ScoreGraph from "./score_graph" +import ScoreTable from "./score_table" + +export default function Scores() { + const { slug } = useParams() + if (!slug) { + return
No
+ } + return ( + GetPlayer(slug)} + > + {(player: Player) => ( + + {(startDate, endDate) => { + return ( + GetPlayerGames(player.slug)} + refetchOnWindowFocus={false} + > + {(scores) => { + return ( +
+ + +
+ ) + }} +
+ ) + }} +
+ )} +
+ ) +} diff --git a/front/src/pages/player/sidebar.tsx b/front/src/pages/player/sidebar.tsx index e36c056..6088758 100644 --- a/front/src/pages/player/sidebar.tsx +++ b/front/src/pages/player/sidebar.tsx @@ -1,57 +1,60 @@ -import Player from "../../types/player" - -function Badge({ - imgUrl, - name, - imgHeight = "full", -}: { - imgUrl?: string - name?: string - imgHeight?: string -}) { - return ( -
- - {name} -
- ) -} - -export default function Sidebar({ player }: { player: Player }) { - return ( -
-
- -
- - - {player.displayName} - -
- - {new Date().getFullYear() - new Date(player.birthDate).getFullYear()} - - - {new Date(player.birthDate).toLocaleDateString("en-GB", { - year: "numeric", - month: "2-digit", - day: "2-digit", - })} - -
- - - -
- ) -} +import { Link } from "react-router-dom" +import Player from "../../types/player" + +function Badge({ + imgUrl, + name, + imgHeight = "full", +}: { + imgUrl?: string + name?: string + imgHeight?: string +}) { + return ( +
+ + {name} +
+ ) +} + +export default function Sidebar({ player }: { player: Player }) { + return ( +
+
+ +
+ + + {player.displayName} + +
+ + {new Date().getFullYear() - new Date(player.birthDate).getFullYear()} + + + {new Date(player.birthDate).toLocaleDateString("en-GB", { + year: "numeric", + month: "2-digit", + day: "2-digit", + })} + +
+ + + + + +
+ ) +} diff --git a/front/src/pages/player/summary.tsx b/front/src/pages/player/summary.tsx new file mode 100644 index 0000000..953a29c --- /dev/null +++ b/front/src/pages/player/summary.tsx @@ -0,0 +1,12 @@ +export default function Summary() { + return ( +
+ Summary + +

+ Ceci est un résumé du joueur. Plus de détails seront ajoutés + prochainement. +

+
+ ) +} diff --git a/front/src/pages/singlerankings/components/progress_bar.tsx b/front/src/pages/singlerankings/components/progress_bar.tsx index d8caf03..dc64e7d 100644 --- a/front/src/pages/singlerankings/components/progress_bar.tsx +++ b/front/src/pages/singlerankings/components/progress_bar.tsx @@ -1,67 +1,67 @@ -interface ProgressBarProps { - percentages: number[]; - colors?: string[]; - textColors?: string[]; - displayPercentage?: boolean; -} - -export default function ProgressBar({ - percentages, - colors, - textColors, - displayPercentage = true, -}: ProgressBarProps) { - if (!colors) { - colors = ["bg-green-300", "bg-yellow-400", "bg-orange-500", "bg-red-600"]; - } - if (!textColors) { - textColors = ["text-black", "text-black", "text-white", "text-white"]; - } - const getColor = (index: number) => { - return colors ? colors[index] || "bg-neutral-100" : "bg-neutral-100"; - }; - const getTextColor = (index: number) => textColors?.[index] || "text-black"; - - const sortedPercentages = [...percentages].sort((a, b) => b - a); - return ( -
-
- {sortedPercentages.map((percentage, index) => { - const spanleft = - 100 - - (((percentage - sortedPercentages[index + 1]) / 2) * 100) / - percentage; - - return ( -
- {displayPercentage && - percentage > 0 && - (spanleft < 96 || isNaN(spanleft)) && ( - - {percentage}% - - )} -
- ); - })} -
- ); -} +interface ProgressBarProps { + percentages: number[]; + colors?: string[]; + textColors?: string[]; + displayPercentage?: boolean; +} + +export default function ProgressBar({ + percentages, + colors, + textColors, + displayPercentage = true, +}: ProgressBarProps) { + if (!colors) { + colors = ["bg-green-300", "bg-yellow-400", "bg-orange-500", "bg-red-600"]; + } + if (!textColors) { + textColors = ["text-black", "text-black", "text-white", "text-white"]; + } + const getColor = (index: number) => { + return colors ? colors[index] || "bg-neutral-100" : "bg-neutral-100"; + }; + const getTextColor = (index: number) => textColors?.[index] || "text-black"; + + const sortedPercentages = [...percentages].sort((a, b) => b - a); + return ( +
+
+ {sortedPercentages.map((percentage, index) => { + const spanleft = + 100 - + (((percentage - sortedPercentages[index + 1]) / 2) * 100) / + percentage; + + return ( +
+ {displayPercentage && + percentage > 0 && + (spanleft < 96 || isNaN(spanleft)) && ( + + {percentage}% + + )} +
+ ); + })} +
+ ); +} diff --git a/front/src/pages/singlerankings/index.tsx b/front/src/pages/singlerankings/index.tsx index 7f138ee..a72dec7 100644 --- a/front/src/pages/singlerankings/index.tsx +++ b/front/src/pages/singlerankings/index.tsx @@ -80,6 +80,26 @@ const orders = [ value: "more_than_10_aa_percentage DESC", label: "AA Percentage > 10 (High to Low)", }, + { + value: "duel_won_per_90_min DESC", + label: "Duel Won Per 90 Min (High to Low)", + }, + { + value: "effective_clearance_per_90_min DESC", + label: "Effective Clearance Per 90 Min (High to Low)", + }, + { + value: "shot_on_target_per_90_min DESC", + label: "Shot On Target Per 90 Min (High to Low)", + }, + { + value: "accurate_pass_per_90_min DESC", + label: "Accurate Pass Per 90 Min (High to Low)", + }, + { + value: "won_contest_per_90_min DESC", + label: "Won Contest Per 90 Min (High to Low)", + }, ] function SingleRankingsPage() { @@ -399,8 +419,35 @@ function SingleRankingsPage() { AA/Min + {usedParams.order.startsWith("duel_won_per_90_min") && ( + + Duel/90 + + )} + {usedParams.order.startsWith( + "effective_clearance_per_90_min" + ) && ( + + EC/90 + + )} + {usedParams.order.startsWith("shot_on_target_per_90_min") && ( + + Shot/90 + + )} + {usedParams.order.startsWith("accurate_pass_per_90_min") && ( + + AP/90 + + )} + {usedParams.order.startsWith("won_contest_per_90_min") && ( + + WC/90 + + )} - Total Min + Total Minutes
@@ -444,7 +491,7 @@ function SingleRankingsPage() { {data?.map((item) => { return ( - +
@@ -461,17 +508,13 @@ function SingleRankingsPage() {
- + - + {item.player.displayName} @@ -502,7 +545,7 @@ function SingleRankingsPage() {
- +
- + ±{item.totalStddevScore} - + {item.decisiveAvgScore} - + {item.allAroundAvgScore} - +
- + {item.percentageMinutesPlayed} {["Goalkeeper", "Defender"].includes( usedParams.position ) && ( - + {item.avgTeamGoalsAgainst} )} {["Midfielder", "Forward"].includes( usedParams.position ) && ( - + {item.avgTeamGoalsFor} )} - + {item.aaPerMin} - + {usedParams.order.startsWith("duel_won_per_90_min") && ( + + {item.duelWonPer90Min} + + )} + {usedParams.order.startsWith( + "effective_clearance_per_90_min" + ) && ( + + {item.effectiveClearancePer90Min} + + )} + {usedParams.order.startsWith( + "shot_on_target_per_90_min" + ) && ( + + {item.shotOnTargetPer90Min} + + )} + {usedParams.order.startsWith( + "accurate_pass_per_90_min" + ) && ( + + {item.accuratePassPer90Min} + + )} + {usedParams.order.startsWith("won_contest_per_90_min") && ( + + {item.wonContestPer90Min} + + )} + {item.totalMinutes} - + - + void; -} - -export default function PositionSelect({ - params, - setParams, -}: PositionSelectProps) { - const handlePositionChange = (position: string) => { - setParams({ - ...params, - position, - }); - }; - - return ( -
- {positions.map((position, index) => ( -
0 ? "border-l-2 border-gray-300" : ""}`} - > - -
- ))} -
- ); -} +import { SingleRankingsParams } from "../../api/rankings"; + +const positions = ["Goalkeeper", "Defender", "Midfielder", "Forward"]; + +interface PositionSelectProps { + params: SingleRankingsParams; + setParams: (params: SingleRankingsParams) => void; +} + +export default function PositionSelect({ + params, + setParams, +}: PositionSelectProps) { + const handlePositionChange = (position: string) => { + setParams({ + ...params, + position, + }); + }; + + return ( +
+ {positions.map((position, index) => ( +
0 ? "border-l-2 border-gray-300" : ""}`} + > + +
+ ))} +
+ ); +} diff --git a/front/src/pages/singlerankings/zone_select.tsx b/front/src/pages/singlerankings/zone_select.tsx index f37ba69..098b7cd 100644 --- a/front/src/pages/singlerankings/zone_select.tsx +++ b/front/src/pages/singlerankings/zone_select.tsx @@ -1,51 +1,51 @@ -import { useQuery } from "@tanstack/react-query"; -import { SingleRankingsParams } from "../../api/rankings"; -import { GetAllZones } from "../../api/zone"; -import Zone from "../../types/zone"; - -interface ZoneSelectProps { - params: SingleRankingsParams; - setParams: (params: SingleRankingsParams) => void; -} - -export default function ZoneSelect({ params, setParams }: ZoneSelectProps) { - const { data: zones } = useQuery({ - queryKey: ["zones"], - queryFn: async () => { - const zones = await GetAllZones(); - return zones.sort((a, b) => a.displayName.localeCompare(b.displayName)); - }, - }); - - const handleZoneChange = (zone: Zone) => { - const newZones = params.zones.includes(zone.id) - ? params.zones.filter((z) => z !== zone.id) - : [...params.zones, zone.id]; - setParams({ - ...params, - zones: newZones, - }); - }; - - return ( -
- {zones?.map((zone, index) => ( -
0 ? "border-l-2 border-gray-300" : ""}`} - > - -
- ))} -
- ); -} +import { useQuery } from "@tanstack/react-query"; +import { SingleRankingsParams } from "../../api/rankings"; +import { GetAllZones } from "../../api/zone"; +import Zone from "../../types/zone"; + +interface ZoneSelectProps { + params: SingleRankingsParams; + setParams: (params: SingleRankingsParams) => void; +} + +export default function ZoneSelect({ params, setParams }: ZoneSelectProps) { + const { data: zones } = useQuery({ + queryKey: ["zones"], + queryFn: async () => { + const zones = await GetAllZones(); + return zones.sort((a, b) => a.displayName.localeCompare(b.displayName)); + }, + }); + + const handleZoneChange = (zone: Zone) => { + const newZones = params.zones.includes(zone.id) + ? params.zones.filter((z) => z !== zone.id) + : [...params.zones, zone.id]; + setParams({ + ...params, + zones: newZones, + }); + }; + + return ( +
+ {zones?.map((zone, index) => ( +
0 ? "border-l-2 border-gray-300" : ""}`} + > + +
+ ))} +
+ ); +} diff --git a/front/src/pages/team/index.tsx b/front/src/pages/team/index.tsx new file mode 100644 index 0000000..3d35d22 --- /dev/null +++ b/front/src/pages/team/index.tsx @@ -0,0 +1,49 @@ +import { Outlet, useLocation, useParams } from "react-router" +import { Link } from "react-router-dom" +import { GetTeam } from "../../api/team" +import WithDataFetching from "../../components/withdatafetching" +import Team from "../../types/team" +import Sidebar from "./sidebar" + +export default function TeamPage() { + const { slug } = useParams() + + if (!slug) { + return
No slug
+ } + return ( + GetTeam(slug)}> + {(team) => } + + ) + + function TeamLayout({ team }: { team: Team }) { + const { pathname } = useLocation() + const tabs = [ + { id: "roster", title: "Roster" }, + { id: "transfers", title: "Transfers" }, + ] + return ( +
+ +
+
+ {tabs.map((tab) => ( + +
+ {tab.title} +
+ + ))} +
+
+ +
+
+
+ ) + } +} diff --git a/front/src/pages/team/roster.tsx b/front/src/pages/team/roster.tsx new file mode 100644 index 0000000..1a8137b --- /dev/null +++ b/front/src/pages/team/roster.tsx @@ -0,0 +1,86 @@ +import { useMemo } from "react" +import { Link, useParams } from "react-router-dom" +import { GetTeamRoster } from "../../api/team" +import man from "../../assets/man.png" +import WithDataFetching from "../../components/withdatafetching" +import { GamePlayer } from "../../types/game" +import Player from "../../types/player" +import Team from "../../types/team" + +export default function Roster() { + const { slug } = useParams() + if (!slug) { + return
No
+ } + return ( + GetTeamRoster(slug)} + > + {(resp) => } + + ) +} + +function RosterLayout({ team, roster }: { team: Team; roster: Player[] }) { + const threeMonthsAgo = new Date() + threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3) + const totalTeamMinutes = useMemo(() => { + return team.homeGames?.concat(team.awayGames)?.reduce((total, game) => { + if (new Date(game.date) >= threeMonthsAgo) { + return total + (game.minutes > 115 ? 120 : 90) + } + return total + }, 0) + }, [team]) + + const computeTotalMinutes = (gamePlayers: GamePlayer[]) => { + return gamePlayers.reduce((total, game) => { + if (game.game && new Date(game.game.date) >= threeMonthsAgo) { + return total + (game.score?.minutesPlayed || 0) + } + return total + }, 0) + } + + const rosterWithMinutes = roster.map((player) => ({ + ...player, + totalMinutesPlayed: computeTotalMinutes(player.gamePlayers), + minutesPlayedPercentage: + (computeTotalMinutes(player.gamePlayers) / totalTeamMinutes) * 100, + })) + + return ( +
+ {["Goalkeeper", "Defender", "Midfielder", "Forward"].map((position) => ( + <> +
+ {position}s +
+
+ {rosterWithMinutes + .filter((player) => player.fieldPosition === position) + .sort((a, b) => b.totalMinutesPlayed - a.totalMinutesPlayed) + .map((player) => ( +
+ + {""} + +
{player.displayName}
+
{player.totalMinutesPlayed}
+
{player.minutesPlayedPercentage.toFixed(1)}%
+
+ ))} +
+ + ))} +
+ ) +} diff --git a/front/src/pages/team/routes.tsx b/front/src/pages/team/routes.tsx new file mode 100644 index 0000000..21b191b --- /dev/null +++ b/front/src/pages/team/routes.tsx @@ -0,0 +1,22 @@ +import TeamPage from "." +import Roster from "./roster" +import Transfers from "./transfers" + +export const teamRoutes = { + path: "team/:slug", + element: , + children: [ + { + path: "", + element: , + }, + { + path: "roster", + element: , + }, + { + path: "transfers", + element: , + }, + ], +} diff --git a/front/src/pages/team/sidebar.tsx b/front/src/pages/team/sidebar.tsx new file mode 100644 index 0000000..6fa6277 --- /dev/null +++ b/front/src/pages/team/sidebar.tsx @@ -0,0 +1,28 @@ +import Badge from "../../components/badge" +import Team from "../../types/team" + +export default function Sidebar({ team }: { team: Team }) { + return ( +
+
+ +
+ + + {team.displayName} + + + +
+ ) +} diff --git a/front/src/pages/team/transfers.tsx b/front/src/pages/team/transfers.tsx new file mode 100644 index 0000000..8400643 --- /dev/null +++ b/front/src/pages/team/transfers.tsx @@ -0,0 +1,249 @@ +import React from "react" +import { FaPersonWalkingArrowRight } from "react-icons/fa6" +import { Link, useParams } from "react-router-dom" +import { GetTeamMemberships } from "../../api/team" +import man from "../../assets/man.png" +import Badge from "../../components/badge" +import WithDataFetching from "../../components/withdatafetching" +import { Membership } from "../../types/player" +import Team from "../../types/team" + +export default function Transfers() { + const { slug } = useParams() + if (!slug) { + return
No team slug
+ } + + return ( + GetTeamMemberships(slug)} + > + {(resp) => ( + + )} + + ) +} + +function TransfersLayout({ + memberships, +}: { + team: Team + memberships: Membership[] +}) { + type YearMonthAccumulator = { + [key: string]: { + arrivals: Membership[] + departures: Membership[] + } + } + const groupedByYearMonth = memberships.reduce( + (acc, membership) => { + const startYearMonth = new Date(membership.startDate) + .toISOString() + .slice(0, 7) + const endYearMonth = membership.endDate + ? new Date(membership.endDate).toISOString().slice(0, 7) + : null + + if (!acc[startYearMonth]) { + acc[startYearMonth] = { arrivals: [], departures: [] } + } + acc[startYearMonth].arrivals.push(membership) + + if (endYearMonth) { + if (!acc[endYearMonth]) { + acc[endYearMonth] = { arrivals: [], departures: [] } + } + acc[endYearMonth].departures.push(membership) + } + + return acc + }, + {} + ) + + // Sort each group by startDate and then by playerSlug + Object.keys(groupedByYearMonth).forEach((yearMonth) => { + groupedByYearMonth[yearMonth].arrivals.sort((a, b) => { + if (a.startDate === b.startDate) { + return a.playerSlug.localeCompare(b.playerSlug) + } + return a.startDate < b.startDate ? -1 : 1 + }) + groupedByYearMonth[yearMonth].departures.sort((a, b) => { + if (a.startDate === b.startDate) { + return a.playerSlug.localeCompare(b.playerSlug) + } + return a.startDate < b.startDate ? -1 : 1 + }) + }) + + const sortedYearMonths = Object.keys(groupedByYearMonth).sort().reverse() + + return ( +
+ {sortedYearMonths.map((yearMonth, index, array) => { + const { arrivals, departures } = groupedByYearMonth[yearMonth] + const year = yearMonth.slice(0, 4) + const isYearBoundary = + index === 0 || year !== array[index - 1].slice(0, 4) + const isEvenMonth = index % 2 === 0 + + return ( + + {isYearBoundary && ( +
+ {year} +
+ )} +
+
+ {arrivals.map((m) => { + const previousMembership = m.player?.clubMembership + ?.filter( + (membership) => membership.membershipType === "club" + ) + .sort((a, b) => { + if (!a.endDate && !b.endDate) { + return ( + new Date(b.startDate).getTime() - + new Date(a.startDate).getTime() + ) + } + if (!a.endDate) return -1 + if (!b.endDate) return 1 + if ( + new Date(a.endDate).getTime() === + new Date(b.endDate).getTime() + ) { + return ( + new Date(b.startDate).getTime() - + new Date(a.startDate).getTime() + ) + } + return ( + new Date(b.endDate).getTime() - + new Date(a.endDate).getTime() + ) + }) + .find( + (membership) => + new Date(membership.startDate) < new Date(m.startDate) + ) + return ( +
+ + {m.player?.displayName} + +
+ + {m.player?.displayName} + + + {previousMembership && previousMembership.team ? ( + <> + + + + ) : ( + "No previous club" + )} + +
+
+ ) + })} +
+
+ + {new Date(yearMonth + "-01") + .toLocaleString(undefined, { month: "long" }) + .replace(/^\w/, (c) => c.toUpperCase())} + +
+
+ {departures.map((m) => { + const nextMembership = m.player?.clubMembership + ?.filter( + (membership) => membership.membershipType === "club" + ) + .sort((a, b) => { + if (!a.endDate && !b.endDate) { + return ( + new Date(b.startDate).getTime() - + new Date(a.startDate).getTime() + ) + } + if (!a.endDate) return -1 + if (!b.endDate) return 1 + if ( + new Date(a.endDate).getTime() === + new Date(b.endDate).getTime() + ) { + return ( + new Date(b.startDate).getTime() - + new Date(a.startDate).getTime() + ) + } + return ( + new Date(b.endDate).getTime() - + new Date(a.endDate).getTime() + ) + }) + .reverse() + .find( + (membership) => + new Date(membership.startDate) >= new Date(m.endDate) + ) + return ( +
+ + {m.player?.displayName} + +
+ + {m.player?.displayName} + + + {nextMembership && nextMembership.team ? ( + <> + + + + ) : ( + "No next club" + )} + +
+
+ ) + })} +
+
+
+ ) + })} +
+ ) +} diff --git a/front/src/pages/xpcenter/index.tsx b/front/src/pages/xpcenter/index.tsx index b2c9969..866a431 100644 --- a/front/src/pages/xpcenter/index.tsx +++ b/front/src/pages/xpcenter/index.tsx @@ -1,205 +1,299 @@ -import { useMemo, useState } from "react" -import { useParams } from "react-router" -import { Link } from "react-router-dom" -import { GetUserCards } from "../../api/card" -import WithDataFetching from "../../components/withdatafetching" -import Card from "../../types/card" - -export default function XpCenter() { - const { slug = "gigiz22" } = useParams() - - return ( - - queryKey={["user", slug, "cards"]} - queryFn={() => GetUserCards(slug)} - refetchOnWindowFocus={false} - > - {(data) => } - - ) -} - -function CardList({ cards }: { cards: Card[] }) { - const [sortConfig, setSortConfig] = useState<{ - key: string - direction: "asc" | "desc" - } | null>(null) - - const [hideMaxLevelUp, setHideMaxLevelUp] = useState(false) - - const filteredCards = useMemo(() => { - return hideMaxLevelUp - ? cards.filter( - (card) => card.levelUpAppliedCount < card.maxLevelUpAppliedCount - ) - : cards - }, [cards, hideMaxLevelUp]) - - const sortedCards = useMemo(() => { - let sortableCards = filteredCards.map((card) => ({ - ...card, - xpNeeded: - card.xpNeededForNextGrade > card.xp - ? card.xpNeededForNextGrade - card.xp - : "N/A", - xpPercentage: - card.xpNeededForNextGrade > card.xp - ? ((card.xpNeededForNextGrade - card.xp) * 100) / - (card.xpNeededForNextGrade - card.xpNeededForCurrentGrade) - : "N/A", - })) - - if (sortConfig !== null) { - sortableCards.sort((a, b) => { - const key = sortConfig.key as keyof typeof a - if (a[key] < b[key]) { - return sortConfig.direction === "asc" ? -1 : 1 - } - if (a[key] > b[key]) { - return sortConfig.direction === "asc" ? 1 : -1 - } - return 0 - }) - } - return sortableCards - }, [filteredCards, sortConfig]) - - const requestSort = (key: string) => { - let direction: "asc" | "desc" = "asc" - if ( - sortConfig && - sortConfig.key === key && - sortConfig.direction === "asc" - ) { - direction = "desc" - } - setSortConfig({ key, direction }) - } - - const getSortIndicator = (key: string) => { - if (!sortConfig || sortConfig.key !== key) return null - return sortConfig.direction === "asc" ? "↑" : "↓" - } - - return ( - <> -
- -
- - - - - - - - - - - - - - {sortedCards.map((card) => ( - - - - - - - - - - ))} - -
- Player - requestSort("grade")} - > - Grade{" "} - - {getSortIndicator("grade")} - - requestSort("xp")} - > - XP{" "} - - {getSortIndicator("xp")} - - requestSort("xpNeeded")} - > - XP Needed for Next Grade{" "} - - {getSortIndicator("xpNeeded")} - - requestSort("xpPercentage")} - > - % Until next grade{" "} - - {getSortIndicator("xpPercentage")} - - - Level up applied -
- - {card.name} - - - -
-
- {card.playerDisplayName} -
-
- -
-
{card.grade}
-
-
{card.xp}
-
-
{card.xpNeeded}
-
-
- {card.xpPercentage !== "N/A" - ? `${Number(card.xpPercentage).toFixed(0)}%` - : "N/A"} -
-
-
- {card.levelUpAppliedCount} / {card.maxLevelUpAppliedCount} -
-
- - ) -} +import { useEffect, useMemo, useState } from "react" +import { FaArrowAltCircleUp } from "react-icons/fa" +import { IoMdFootball } from "react-icons/io" +import { IoManSharp } from "react-icons/io5" +import { useParams } from "react-router" +import { Link } from "react-router-dom" +import Select from "react-select" +import { GetUserCards } from "../../api/card" +import WithDataFetching from "../../components/withdatafetching" +import Card from "../../types/card" + +export default function XpCenter() { + const { slug = "gigiz22" } = useParams() + + return ( + + queryKey={["user", slug, "cards"]} + queryFn={() => GetUserCards(slug)} + refetchOnWindowFocus={false} + > + {(data) => } + + ) +} + +function CardList({ cards }: { cards: Card[] }) { + const [sortConfig, setSortConfig] = useState<{ + key: string + direction: "asc" | "desc" + }>( + () => + JSON.parse( + localStorage.getItem("sortConfig") || + '{"key": "xpPercentage", "direction": "desc"}' + ) as { + key: string + direction: "asc" | "desc" + } + ) + + const [hideMaxLevelUp, setHideMaxLevelUp] = useState(() => + JSON.parse(localStorage.getItem("hideMaxLevelUp") || "true") + ) + const [hideMaxLevelCards, setHideMaxLevelCards] = useState(() => + JSON.parse(localStorage.getItem("hideMaxLevelCards") || "true") + ) + const [minGrade, setMinGrade] = useState(() => + JSON.parse(localStorage.getItem("minGrade") || "0") + ) + const [onlyInSeason, setOnlyInSeason] = useState(() => + JSON.parse(localStorage.getItem("onlyInSeason") || "false") + ) + + useEffect(() => { + localStorage.setItem("sortConfig", JSON.stringify(sortConfig)) + }, [sortConfig]) + + useEffect(() => { + localStorage.setItem("hideMaxLevelUp", JSON.stringify(hideMaxLevelUp)) + }, [hideMaxLevelUp]) + + useEffect(() => { + localStorage.setItem("hideMaxLevelCards", JSON.stringify(hideMaxLevelCards)) + }, [hideMaxLevelCards]) + + useEffect(() => { + localStorage.setItem("minGrade", JSON.stringify(minGrade)) + }, [minGrade]) + + useEffect(() => { + localStorage.setItem("onlyInSeason", JSON.stringify(onlyInSeason)) + }, [onlyInSeason]) + + const filteredCards = useMemo(() => { + return cards.filter((card) => { + if ( + hideMaxLevelUp && + card.levelUpAppliedCount >= card.maxLevelUpAppliedCount + ) { + return false + } + if (hideMaxLevelCards && card.grade >= 20) { + return false + } + if (card.grade < minGrade) { + return false + } + if (onlyInSeason && !card.inSeasonEligible) { + return false + } + return true + }) + }, [cards, hideMaxLevelUp, hideMaxLevelCards, minGrade, onlyInSeason]) + + const sortedCards = useMemo(() => { + let sortableCards = filteredCards.map((card) => ({ + ...card, + xpNeeded: + card.xpNeededForNextGrade > card.xp + ? card.xpNeededForNextGrade - card.xp + : "N/A", + xpPercentage: + card.xpNeededForNextGrade > card.xp + ? ((card.xpNeededForNextGrade - card.xp) * 100) / + (card.xpNeededForNextGrade - card.xpNeededForCurrentGrade) + : "N/A", + })) + + if (sortConfig !== null) { + sortableCards.sort((a, b) => { + const key = sortConfig.key as keyof typeof a + if (a[key] === "N/A") return 1 + if (b[key] === "N/A") return -1 + if (Number(a[key]) < Number(b[key])) { + return sortConfig.direction === "asc" ? -1 : 1 + } + if (Number(a[key]) > Number(b[key])) { + return sortConfig.direction === "asc" ? 1 : -1 + } + return 0 + }) + } + return sortableCards + }, [filteredCards, sortConfig]) + + const handleSortFieldChange = (selectedOption: any) => { + setSortConfig((prevConfig) => ({ + key: selectedOption.value, + direction: prevConfig?.direction || "asc", + })) + } + + const handleSortDirectionChange = (selectedOption: any) => { + setSortConfig((prevConfig) => ({ + key: prevConfig?.key || "", + direction: selectedOption.value, + })) + } + + const sortFieldOptions = [ + { value: "grade", label: "Grade" }, + { value: "xp", label: "XP" }, + { value: "xpNeeded", label: "XP Needed for Next Grade" }, + { value: "xpPercentage", label: "% Until next grade" }, + ] + + const sortDirectionOptions = [ + { value: "asc", label: "Ascending" }, + { value: "desc", label: "Descending" }, + ] + + return ( +
+
+ + + + + + + + + option.value === sortConfig.direction + )} + placeholder="Sort direction" + /> +
+
+ {sortedCards.map((card) => ( +
+ + {card.name} + + +
+
+ + + + + + +
+
+ + {card.grade} + + + {card.grade < 20 + ? `${card.xp - card.xpNeededForCurrentGrade} / ${card.xpNeededForNextGrade - card.xpNeededForCurrentGrade}` + : card.xp} + + + {Number((card.power - 1) * 100).toFixed(1)}% + +
+
+
+
+
+ + {card.grade < 20 + ? `${Number(card.xpPercentage).toFixed(1)}%` + : ""} + +
+
+
+ + + +
+
+ {card.inSeasonEligible ? ( + + In Season + + ) : ( +   + )} +
+
+ ))} +
+
+ ) +} diff --git a/front/src/router/router.tsx b/front/src/router/router.tsx index c7f8ede..98f6854 100644 --- a/front/src/router/router.tsx +++ b/front/src/router/router.tsx @@ -1,8 +1,9 @@ import { createBrowserRouter, Link, RouterProvider } from "react-router-dom" import Layout from "../pages/layout" import Live from "../pages/live" -import PlayerPage from "../pages/player" +import { playerRoutes } from "../pages/player/routes" import SingleRankingsPage from "../pages/singlerankings" +import { teamRoutes } from "../pages/team/routes" import XpCenter from "../pages/xpcenter" const router = createBrowserRouter([ @@ -10,14 +11,18 @@ const router = createBrowserRouter([ path: "/", element: , children: [ - { path: "/", element: }, - { path: "/live", element: }, - { path: "/xpcenter", element: }, - { path: "/xpcenter/:slug", element: }, - { path: "/player/:slug", element: }, + { path: "", element: }, + { path: "live", element: }, + { path: "xpcenter", element: }, + { path: "xpcenter/:slug", element: }, + playerRoutes, + teamRoutes, ], }, - { path: "*", element: }, + { + path: "*", + element: , + }, ]) export default function RouterOutlet() { diff --git a/front/src/types/card.ts b/front/src/types/card.ts index c1f5400..a0af995 100644 --- a/front/src/types/card.ts +++ b/front/src/types/card.ts @@ -1,27 +1,27 @@ -export default interface Card { - id: string - assetId: string - playerSlug: string - playerDisplayName: string - rarity: string - serialNumber: number - seasonStartYear: number - singleCivilYear: boolean - supply: number - teamSlug: string - name: string - pictureUrl: string - slug: string - power: number - powerMalusAfterTransfer: number - rivalsPower: number - grade: number - gradeAfterTransfer: number - xp: number - xpAfterTransfer: number - xpNeededForNextGrade: number - xpNeededForCurrentGrade: number - inSeasonEligible: boolean - levelUpAppliedCount: number - maxLevelUpAppliedCount: number -} +export default interface Card { + id: string + assetId: string + playerSlug: string + playerDisplayName: string + rarity: string + serialNumber: number + seasonStartYear: number + singleCivilYear: boolean + supply: number + teamSlug: string + name: string + pictureUrl: string + slug: string + power: number + powerMalusAfterTransfer: number + rivalsPower: number + grade: number + gradeAfterTransfer: number + xp: number + xpAfterTransfer: number + xpNeededForNextGrade: number + xpNeededForCurrentGrade: number + inSeasonEligible: boolean + levelUpAppliedCount: number + maxLevelUpAppliedCount: number +} diff --git a/front/src/types/competition.ts b/front/src/types/competition.ts index 4e33d8e..6473aec 100644 --- a/front/src/types/competition.ts +++ b/front/src/types/competition.ts @@ -1,15 +1,15 @@ -import Country from "./country" -import Zone from "./zone" - -export default interface Competition { - slug: string - displayName: string - countrySlug: string - competitionFormat: string - competitionType: string - pictureUrl: string - logoUrl: string - zoneId?: number - zone?: Zone - country: Country -} +import Country from "./country" +import Zone from "./zone" + +export default interface Competition { + slug: string + displayName: string + countrySlug: string + competitionFormat: string + competitionType: string + pictureUrl: string + logoUrl: string + zoneId?: number + zone?: Zone + country: Country +} diff --git a/front/src/types/country.ts b/front/src/types/country.ts index 85f6cc7..99e2c3b 100644 --- a/front/src/types/country.ts +++ b/front/src/types/country.ts @@ -1,11 +1,11 @@ -export default interface Country { - slug: string; - code: string; - displayName: string; - threeLetterCode: string; - flagFlat64Url: string; - flagFlat32Url: string; - flagRound64Url: string; - flagRound32Url: string; -} - +export default interface Country { + slug: string; + code: string; + displayName: string; + threeLetterCode: string; + flagFlat64Url: string; + flagFlat32Url: string; + flagRound64Url: string; + flagRound32Url: string; +} + diff --git a/front/src/types/fixture.ts b/front/src/types/fixture.ts index 9294c3c..f780e21 100644 --- a/front/src/types/fixture.ts +++ b/front/src/types/fixture.ts @@ -1,8 +1,8 @@ -export interface Fixture { - slug: string - displayName: string - fixtureState: string - startDate: Date - endDate: Date - gameWeek: number -} +export interface Fixture { + slug: string + displayName: string + fixtureState: string + startDate: Date + endDate: Date + gameWeek: number +} diff --git a/front/src/types/game.ts b/front/src/types/game.ts index 898e95d..d16fcc9 100644 --- a/front/src/types/game.ts +++ b/front/src/types/game.ts @@ -1,177 +1,178 @@ -import Competition from "./competition" -import { Fixture } from "./fixture" -import Player from "./player" -import Team from "./team" - -export interface Game { - id: string - date: Date - coverageStatus: string - lowCoverage: boolean - minutes: number - periodType: string - scored: boolean - status: string - competitionSlug: string - fixtureSlug: string - awayTeamSlug: string - awayGoals: number - awayExtraTimeScore: number - awayPenaltyScore: number - homeTeamSlug: string - homeGoals: number - homeExtraTimeScore: number - homePenaltyScore: number - winnerTeamSlug: string - competition: Competition - homeTeam: Team - awayTeam: Team - winnerTeam: Team - gamePlayers: GamePlayer[] - fixture: Fixture -} - -export interface GamePlayer { - gameId: string - playerSlug: string - teamSlug: string - status: string - - game?: Game - player?: Player - score?: GamePlayerScore -} - -export interface GamePlayerScore { - gameId: string - playerSlug: string - score: number - decisiveScore: number - allAroundScore: number - minutesPlayed: number - gameStarted: boolean - formationPlace: number - live: boolean - onGameSheet: boolean - reviewed: boolean - goal: number - assist: number - penaltyWon: number - clearanceOffLine: number - lastManTackle: number - penaltySave: number - ownGoal: number - redCard: boolean - errorLeadToGoal: number - penaltyConceded: number - yellowCard: number - fouls: number - fouled: number - cleanSheet: boolean - doubleDouble: boolean - tripleDouble: boolean - tripleTriple: boolean - errorLeadToShot: number - saves: number - savedShotFromInsideBox: number - goodHighClaim: number - punches: number - divingSave: number - divingCatch: number - crossNotClaimed: number - goalkeeperSmother: number - sixSecondViolation: number - keeperSweeper: number - goalsConceded: number - effectiveClearance: number - wonTackle: number - blockedCross: number - block: number - possessionLost: number - possessionWon: number - duelLost: number - duelWon: number - interception: number - accuratePass: number - accurateFinalThirdPass: number - accurateLongBall: number - longPassIntoOpposition: number - missedPass: number - shotOnTarget: number - wonContest: number - bigChanceCreated: number - attemptedAssist: number - penaltyAreaEntries: number - penaltyKickMissed: number - bigChanceMissed: number -} - -export function GetScoreColor(score: number, tailwind: boolean): string { - if (score < 15) { - return tailwind ? "bg-support-red-400" : "#FF0000" // red - } else if (score < 40) { - return tailwind ? "bg-orange-400" : "#FFA500" // orange - } else if (score < 60) { - return tailwind ? "bg-yellow-400" : "#FFFF00" // yellow - } else if (score < 75) { - return tailwind ? "bg-green-600" : "#32CD32" // lime green, a bit darker than light green - } else { - return tailwind ? "bg-green-700" : "#006400" // dark green - } -} - -export function GetLScoreColor(score: number): string { - if (score < 40) { - return "bg-support-red-500" - } else if (score < 46) { - return "bg-orange-500" - } else if (score < 54) { - return "bg-yellow-500" - } else if (score < 66) { - return "bg-green-500" - } else { - return "bg-green-600" - } -} - -export function GetDSScoreColor(score: number): string { - if (score < 34) { - return "bg-support-red-500" - } else if (score < 37) { - return "bg-orange-500" - } else if (score < 42) { - return "bg-yellow-500" - } else if (score < 52) { - return "bg-green-500" - } else { - return "bg-green-600" - } -} - -export function GetAAScoreColor(score: number): string { - if (score < 7) { - return "bg-support-red-500" - } else if (score < 10) { - return "bg-orange-500" - } else if (score < 16) { - return "bg-yellow-500" - } else if (score < 25) { - return "bg-green-500" - } else { - return "bg-green-600" - } -} - -export function GetMinutesScoreColor(score: number): string { - if (score < 38) { - return "bg-support-red-500" - } else if (score < 58) { - return "bg-orange-500" - } else if (score < 78) { - return "bg-yellow-500" - } else if (score < 90) { - return "bg-green-500" - } else { - return "bg-green-600" - } -} +import Competition from "./competition" +import { Fixture } from "./fixture" +import Player from "./player" +import Team from "./team" + +export interface Game { + id: string + date: Date + coverageStatus: string + lowCoverage: boolean + minutes: number + periodType: string + scored: boolean + status: string + competitionSlug: string + fixtureSlug: string + awayTeamSlug: string + awayGoals: number + awayExtraTimeScore: number + awayPenaltyScore: number + homeTeamSlug: string + homeGoals: number + homeExtraTimeScore: number + homePenaltyScore: number + winnerTeamSlug: string + competition: Competition + homeTeam: Team + awayTeam: Team + winnerTeam: Team + gamePlayers: GamePlayer[] + fixture: Fixture +} + +export interface GamePlayer { + gameId: string + playerSlug: string + teamSlug: string + status: string + + game?: Game + player?: Player + score?: GamePlayerScore +} + +export interface GamePlayerScore { + gameId: string + playerSlug: string + gameDate: Date + score: number + decisiveScore: number + allAroundScore: number + minutesPlayed: number + gameStarted: boolean + formationPlace: number + live: boolean + onGameSheet: boolean + reviewed: boolean + goal: number + assist: number + penaltyWon: number + clearanceOffLine: number + lastManTackle: number + penaltySave: number + ownGoal: number + redCard: boolean + errorLeadToGoal: number + penaltyConceded: number + yellowCard: number + fouls: number + fouled: number + cleanSheet: boolean + doubleDouble: boolean + tripleDouble: boolean + tripleTriple: boolean + errorLeadToShot: number + saves: number + savedShotFromInsideBox: number + goodHighClaim: number + punches: number + divingSave: number + divingCatch: number + crossNotClaimed: number + goalkeeperSmother: number + sixSecondViolation: number + keeperSweeper: number + goalsConceded: number + effectiveClearance: number + wonTackle: number + blockedCross: number + block: number + possessionLost: number + possessionWon: number + duelLost: number + duelWon: number + interception: number + accuratePass: number + accurateFinalThirdPass: number + accurateLongBall: number + longPassIntoOpposition: number + missedPass: number + shotOnTarget: number + wonContest: number + bigChanceCreated: number + attemptedAssist: number + penaltyAreaEntries: number + penaltyKickMissed: number + bigChanceMissed: number +} + +export function GetScoreColor(score: number, tailwind: boolean): string { + if (score < 15) { + return tailwind ? "bg-support-red-400" : "#FF0000" // red + } else if (score < 40) { + return tailwind ? "bg-orange-400" : "#FFA500" // orange + } else if (score < 60) { + return tailwind ? "bg-yellow-400" : "#FFFF00" // yellow + } else if (score < 75) { + return tailwind ? "bg-green-600" : "#32CD32" // lime green, a bit darker than light green + } else { + return tailwind ? "bg-green-700" : "#006400" // dark green + } +} + +export function GetLScoreColor(score: number): string { + if (score < 40) { + return "bg-support-red-500" + } else if (score < 46) { + return "bg-orange-500" + } else if (score < 54) { + return "bg-yellow-500" + } else if (score < 66) { + return "bg-green-500" + } else { + return "bg-green-600" + } +} + +export function GetDSScoreColor(score: number): string { + if (score < 34) { + return "bg-support-red-500" + } else if (score < 37) { + return "bg-orange-500" + } else if (score < 42) { + return "bg-yellow-500" + } else if (score < 52) { + return "bg-green-500" + } else { + return "bg-green-600" + } +} + +export function GetAAScoreColor(score: number): string { + if (score < 7) { + return "bg-support-red-500" + } else if (score < 10) { + return "bg-orange-500" + } else if (score < 16) { + return "bg-yellow-500" + } else if (score < 25) { + return "bg-green-500" + } else { + return "bg-green-600" + } +} + +export function GetMinutesScoreColor(score: number): string { + if (score < 38) { + return "bg-support-red-500" + } else if (score < 58) { + return "bg-orange-500" + } else if (score < 78) { + return "bg-yellow-500" + } else if (score < 90) { + return "bg-green-500" + } else { + return "bg-green-600" + } +} diff --git a/front/src/types/player.ts b/front/src/types/player.ts index 1e5dd5b..6f6f6c7 100644 --- a/front/src/types/player.ts +++ b/front/src/types/player.ts @@ -1,41 +1,45 @@ -import Competition from "./competition" -import Country from "./country" -import Team from "./team" - -interface CardSupply { - playerSlug: string - seasonStartYear: number - limited: number - rare: number - superRare: number - unique: number - lastUpdated: Date -} -interface Membership { - id: string - playerSlug: string - teamSlug: string - startDate: Date - endDate: Date - membershipType: string - - team?: Team -} - -export default interface Player { - slug: string - displayName: string - birthDate: Date - countrySlug: string - teamSlug?: string - domesticLeagueSlug?: string - avatarUrl: string - fieldPosition: string - status: string - shirtNumber: number - country?: Country - team?: Team - domesticLeague?: Competition - cardSupply: CardSupply[] - clubMembership: Membership[] -} +import Competition from "./competition" +import Country from "./country" +import { GamePlayer } from "./game" +import Team from "./team" + +interface CardSupply { + playerSlug: string + seasonStartYear: number + limited: number + rare: number + superRare: number + unique: number + lastUpdated: Date +} +export interface Membership { + id: string + playerSlug: string + teamSlug: string + startDate: Date + endDate: Date + membershipType: string + + player?: Player + team?: Team +} + +export default interface Player { + slug: string + displayName: string + birthDate: Date + countrySlug: string + teamSlug?: string + domesticLeagueSlug?: string + avatarUrl: string + fieldPosition: string + status: string + shirtNumber: number + country?: Country + team?: Team + domesticLeague?: Competition + cardSupply: CardSupply[] + clubMembership: Membership[] + + gamePlayers: GamePlayer[] +} diff --git a/front/src/types/team.ts b/front/src/types/team.ts index 09ba348..c008f5a 100644 --- a/front/src/types/team.ts +++ b/front/src/types/team.ts @@ -1,15 +1,18 @@ -import Competition from "./competition" -import Country from "./country" - -export default interface Team { - slug: string - displayName: string - countrySlug: string - domesticLeagueSlug?: string - shortName: string - pictureUrl: string - teamType: string - - country?: Country - domesticLeague?: Competition -} +import Competition from "./competition" +import Country from "./country" +import { Game } from "./game" + +export default interface Team { + slug: string + displayName: string + countrySlug: string + domesticLeagueSlug?: string + shortName: string + pictureUrl: string + teamType: string + + country?: Country + domesticLeague?: Competition + homeGames: Game[] + awayGames: Game[] +} diff --git a/front/src/types/zone.ts b/front/src/types/zone.ts index 4285b43..465e22b 100644 --- a/front/src/types/zone.ts +++ b/front/src/types/zone.ts @@ -1,4 +1,4 @@ -export default interface Zone { - id: number; - displayName: string; +export default interface Zone { + id: number; + displayName: string; } \ No newline at end of file diff --git a/model/game_player_score.go b/model/game_player_score.go index 2587656..d89a0aa 100644 --- a/model/game_player_score.go +++ b/model/game_player_score.go @@ -1,12 +1,15 @@ package model import ( + "time" + "github.com/shopspring/decimal" ) type GamePlayerScore struct { GameID string `bun:"game_id,pk" json:"gameID"` PlayerSlug string `bun:"player_slug,pk" json:"playerSlug"` + GameDate time.Time `bun:"game_date,pk" json:"gameDate"` Score decimal.Decimal `bun:"score" json:"score"` DecisiveScore decimal.Decimal `bun:"decisive_score" json:"decisiveScore"` AllAroundScore decimal.Decimal `bun:"all_around_score" json:"allAroundScore"` diff --git a/model/player.go b/model/player.go index 37408ce..82338be 100644 --- a/model/player.go +++ b/model/player.go @@ -22,4 +22,6 @@ type Player struct { DomesticLeague *Competition `bun:"rel:has-one,join:domestic_league_slug=slug" json:"domesticLeague"` CardSupply []CardSupply `bun:"rel:has-many,join:slug=player_slug" json:"cardSupply"` ClubMembership []Membership `bun:"rel:has-many,join:slug=player_slug" json:"clubMembership"` + + GamePlayers []GamePlayer `bun:"rel:has-many,join:slug=player_slug" json:"gamePlayers"` } diff --git a/model/team.go b/model/team.go index 70d252a..bd74866 100644 --- a/model/team.go +++ b/model/team.go @@ -11,4 +11,7 @@ type Team struct { Country *Country `bun:"rel:has-one,join:country_slug=slug" json:"country"` DomesticLeague *Competition `bun:"rel:has-one,join:domestic_league_slug=slug" json:"domesticLeague"` + + HomeGames []Game `bun:"rel:has-many,join:slug=home_team_slug" json:"homeGames"` + AwayGames []Game `bun:"rel:has-many,join:slug=away_team_slug" json:"awayGames"` } diff --git a/sorare_utils/competition.go b/sorare_utils/competition.go new file mode 100644 index 0000000..61e823a --- /dev/null +++ b/sorare_utils/competition.go @@ -0,0 +1,26 @@ +package sorare_utils + +import ( + "git.lehouerou.net/laurent/sorare" + "git.lehouerou.net/laurent/sorare/football" + "git.lehouerou.net/laurent/sorarebuddy/db" +) + +type CompetitionUpdater struct { + s *sorare.Sorare + db *db.Client + + countryUpdater *CountryUpdater + cache []football.Competition + slugsToRead []string +} + +func NewCompetitionUpdater(s *sorare.Sorare, db *db.Client, countryUpdater *CountryUpdater) *CompetitionUpdater { + return &CompetitionUpdater{ + s: s, + db: db, + countryUpdater: countryUpdater, + } +} + + diff --git a/sorare_utils/country.go b/sorare_utils/country.go new file mode 100644 index 0000000..2cebc78 --- /dev/null +++ b/sorare_utils/country.go @@ -0,0 +1,93 @@ +package sorare_utils + +import ( + "context" + + "git.lehouerou.net/laurent/sorare" + "git.lehouerou.net/laurent/sorare/graphql" + "git.lehouerou.net/laurent/sorarebuddy/db" + "git.lehouerou.net/laurent/sorarebuddy/model" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/samber/lo" +) + +func NewCountryFromSorare(s sorare.Country) model.Country { + return model.Country{ + Slug: s.Slug, + Code: s.Code, + DisplayName: s.Name, + ThreeLetterCode: s.ThreeLetterCode, + FlagFlat64Url: s.FlagFlat64Url, + FlagFlat32Url: s.FlagFlat32Url, + FlagRound64Url: s.FlagRound64Url, + FlagRound32Url: s.FlagRound32Url, + } +} + +type CountryUpdater struct { + db *db.Client + s *sorare.Sorare + cache []sorare.Country + slugsToRead []string +} + +func NewCountryUpdater(s *sorare.Sorare, db *db.Client) *CountryUpdater { + return &CountryUpdater{db: db, s: s} +} + +func (u *CountryUpdater) AddSlugsToRead(slugs []string) { + slugs = lo.Filter(slugs, func(slug string, index int) bool { + return slug != "" + }) + u.slugsToRead = append(u.slugsToRead, slugs...) +} + +func (u *CountryUpdater) Read(ctx context.Context, onlyMissings bool) error { + if len(u.slugsToRead) == 0 { + return nil + } + log.Debug().Msgf("reading %d countries...", len(u.slugsToRead)) + slugs := u.slugsToRead + u.slugsToRead = nil + + if onlyMissings { + log.Debug().Msgf("filtering countries in db...") + slugsNotInDb, err := u.db.Countries.GetCountrySlugsNotInDb(ctx, slugs) + if err != nil { + return errors.Wrap(err, "getting countries not in db") + } + slugs = slugsNotInDb + log.Debug().Msgf("%d countries not in db", len(slugs)) + } + + log.Debug().Msgf("getting countries from sorare...") + u.cache = nil + for i, chunk := range lo.Chunk(slugs, 100) { + log.Debug().Msgf("\tbatch %d/%d", i+1, (len(slugs)/100)+1) + c, err := u.s.Countries.Get(ctx, graphql.SlugsParams{Slugs: chunk}) + if err != nil { + return err + } + u.cache = append(u.cache, c...) + } + log.Debug().Msgf("%d countries fetched from sorare", len(u.cache)) + + return nil +} + +func (u *CountryUpdater) Write(ctx context.Context) error { + log.Debug().Msg("inserting countries into db...") + err := u.db.Countries.CreateOrUpdateMany( + ctx, + lo.Map(u.cache, func(country sorare.Country, index int) model.Country { + return NewCountryFromSorare(country) + }), + ) + if err != nil { + return errors.Wrap(err, "inserting countries") + } + log.Debug().Msgf("%d countries inserted", len(u.cache)) + u.cache = nil + return nil +} diff --git a/sorare_utils/game_player_score.go b/sorare_utils/game_player_score.go index f61cee4..12bac7a 100644 --- a/sorare_utils/game_player_score.go +++ b/sorare_utils/game_player_score.go @@ -15,6 +15,7 @@ func NewGamePlayerScoreFromSorare( res := model.GamePlayerScore{ GameID: gameId, PlayerSlug: s.Player.Slug, + GameDate: s.Game.Date, MinutesPlayed: int(s.PlayerGameStats.MinsPlayed), GameStarted: s.PlayerGameStats.GameStarted == 1, FormationPlace: int(s.PlayerGameStats.FormationPlace), diff --git a/sorare_utils/updater.go b/sorare_utils/updater.go index 0bdd488..de69b17 100644 --- a/sorare_utils/updater.go +++ b/sorare_utils/updater.go @@ -20,7 +20,8 @@ type Updater struct { s *sorare.Sorare db *db.Client - countrySlugsToRead []string + countryUpdater *CountryUpdater + competitionSlugsToRead []string teamSlugsToRead []string playerSlugsToRead []string @@ -29,7 +30,6 @@ type Updater struct { gameFormationsToRead []string gameScoresToReadFromMap map[string][]string - countryCache []sorare.Country competitionCache []football.Competition clubCache []football.Club nationalTeamCache []football.NationalTeam @@ -77,14 +77,16 @@ func WithUpdateOnlyMissingPlayers(value bool) UpdaterOption { func NewUpdater(s *sorare.Sorare, db *db.Client, opts ...UpdaterOption) *Updater { return &Updater{ - s: s, - db: db, + s: s, + db: db, + + countryUpdater: NewCountryUpdater(s, db), + gameScoresToReadFromMap: make(map[string][]string), } } func (u *Updater) Reset() { - u.countrySlugsToRead = nil u.competitionSlugsToRead = nil u.teamSlugsToRead = nil u.playerSlugsToRead = nil @@ -95,7 +97,6 @@ func (u *Updater) Reset() { delete(u.gameScoresToReadFromMap, k) } - u.countryCache = nil u.competitionCache = nil u.clubCache = nil u.nationalTeamCache = nil @@ -144,11 +145,11 @@ func (u *Updater) Update(ctx context.Context, opts ...UpdaterOption) error { return errors.Wrap(err, "reading competitions") } - if err := u.readCountries(ctx, options.UpdateOnlyMissingCountries); err != nil { + if err := u.countryUpdater.Read(ctx, options.UpdateOnlyMissingCountries); err != nil { return errors.Wrap(err, "reading countries") } - if err := u.writeCountries(ctx); err != nil { + if err := u.countryUpdater.Write(ctx); err != nil { return errors.Wrap(err, "writing countries") } @@ -180,68 +181,7 @@ func (u *Updater) Update(ctx context.Context, opts ...UpdaterOption) error { } func (u *Updater) AddCountriesToRead(slugs ...string) { - slugs = lo.Filter(slugs, func(slug string, index int) bool { - return slug != "" - }) - u.countrySlugsToRead = lo.Uniq(append(u.countrySlugsToRead, slugs...)) -} - -func (u *Updater) readCountries(ctx context.Context, onlyMissings bool) error { - if len(u.countrySlugsToRead) == 0 { - log.Debug().Msg("no countries to read") - return nil - } - log.Debug().Msgf("reading %d countries...", len(u.countrySlugsToRead)) - slugs := u.countrySlugsToRead - u.countrySlugsToRead = nil - - if onlyMissings { - log.Debug().Msgf("filtering countries in db...") - slugsNotInDb, err := u.db.Countries.GetCountrySlugsNotInDb(ctx, slugs) - if err != nil { - return errors.Wrap(err, "getting countries not in db") - } - slugs = slugsNotInDb - log.Debug().Msgf("%d countries not in db", len(slugs)) - } - - log.Debug().Msgf("getting countries from sorare...") - u.countryCache = nil - for i, chunk := range lo.Chunk(slugs, 100) { - log.Debug().Msgf("\tbatch %d/%d", i+1, (len(slugs)/100)+1) - c, err := u.s.Countries.Get(ctx, graphql.SlugsParams{Slugs: chunk}) - if err != nil { - return err - } - u.countryCache = append(u.countryCache, c...) - } - log.Debug().Msgf("%d countries fetched from sorare", len(u.countryCache)) - return nil -} - -func (u *Updater) writeCountries(ctx context.Context) error { - log.Debug().Msg("inserting countries into db...") - err := u.db.Countries.CreateOrUpdateMany( - ctx, - lo.Map(u.countryCache, func(country sorare.Country, index int) model.Country { - return model.Country{ - Slug: country.Slug, - Code: country.Code, - DisplayName: country.Name, - ThreeLetterCode: country.ThreeLetterCode, - FlagFlat64Url: country.FlagFlat64Url, - FlagFlat32Url: country.FlagFlat32Url, - FlagRound64Url: country.FlagRound64Url, - FlagRound32Url: country.FlagRound32Url, - } - }), - ) - if err != nil { - return errors.Wrap(err, "inserting countries") - } - log.Debug().Msgf("%d countries inserted", len(u.countryCache)) - u.countryCache = nil - return nil + u.countryUpdater.AddSlugsToRead(slugs) } func (u *Updater) AddCompetitionsToRead(slugs ...string) { @@ -384,7 +324,7 @@ func (u *Updater) readTeams(ctx context.Context, onlyMissings bool) error { func (u *Updater) writeTeams(ctx context.Context) error { log.Debug().Msg("inserting teams into db...") - err := u.db.Teams.CreateOrUpdateMany(ctx, lo.Union( + err := u.db.Teams.CreateOrUpdateMany(ctx, append( lo.Map(u.clubCache, func(club football.Club, index int) model.Team { return model.Team{ Slug: club.Slug, @@ -411,7 +351,7 @@ func (u *Updater) writeTeams(ctx context.Context) error { PictureUrl: nationalTeam.PictureUrl, TeamType: "national", } - }), + })..., )) if err != nil { return errors.Wrap(err, "inserting teams")