diff --git a/.vscode/settings.json b/.vscode/settings.json index dbda57af7d..22f07fabf7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,5 +26,8 @@ "**/.turbo": true, "**/node_modules": true, "**/pnpm-lock.yaml": true + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" } } diff --git a/apps/cms/schemas/index.ts b/apps/cms/schemas/index.ts index 85bc13ea6c..b5552e4223 100644 --- a/apps/cms/schemas/index.ts +++ b/apps/cms/schemas/index.ts @@ -12,11 +12,13 @@ import contactProfile from "./objects/contact-profile"; import question from "./objects/question"; import spotRange from "./objects/spot-range"; import time from "./objects/time"; +import trophy from "./objects/trophy"; import post from "./post"; import profile from "./profile"; import repeatingHappening from "./repeating-happening"; import staticInfo from "./static-info"; import studentGroup from "./student-group"; +import trophies from "./trophies"; export const schemaTypes = [ hsApplication, @@ -38,4 +40,6 @@ export const schemaTypes = [ time, merch, hungerGames, + trophies, + trophy, ]; diff --git a/apps/cms/schemas/objects/trophy.tsx b/apps/cms/schemas/objects/trophy.tsx new file mode 100644 index 0000000000..9e35def6c3 --- /dev/null +++ b/apps/cms/schemas/objects/trophy.tsx @@ -0,0 +1,55 @@ +import { defineField, defineType } from "sanity"; + +export default defineType({ + name: "trophy", + title: "Trofé", + type: "object", + fields: [ + defineField({ + name: "image", + title: "Bilde", + type: "image", + description: "Last opp et bilde for dette spesifikke trofeet.", + options: { hotspot: true }, + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "title", + title: "Tittel", + type: "string", + description: "Fyll inn en spesifikk tittel til dette troféet", + placeholder: "F.eks. 'Øvelseskjøring' eller 'Sølv gokart'", + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "description", + title: "Beskrivelse", + type: "text", + rows: 3, + placeholder: + "F.eks 'Du har vært med på ett arrangement, bli med ett til for å få gull trofé.'", + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "level", + title: "Nivå", + type: "number", + description: + "Talled du gir til nivå vil ikke ha noe å si. Om du ønske flere nivå, betyr lavere tall lavere nivå.", + placeholder: "1", + validation: (Rule) => Rule.required().integer().min(1), + }), + ], + preview: { + select: { title: "title", media: "image", level: "level" }, + prepare({ title, media, level }) { + return { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + title: title ?? "Uten tittel", + subtitle: level ? `Nivå ${level}` : "Uten nivå", + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + media, + }; + }, + }, +}); diff --git a/apps/cms/schemas/trophies.tsx b/apps/cms/schemas/trophies.tsx new file mode 100644 index 0000000000..9a5198826f --- /dev/null +++ b/apps/cms/schemas/trophies.tsx @@ -0,0 +1,70 @@ +import { StarIcon } from "@sanity/icons"; +import { defineArrayMember, defineField, defineType } from "sanity"; + +export default defineType({ + name: "trophies", + title: "Troféer", + type: "document", + icon: StarIcon, + fields: [ + defineField({ + name: "title", + title: "Tittel", + type: "string", + description: "Dette blir en felles tittel, uansett om du har oppnådd troféet eller ikke.", + placeholder: "F.eks. Gokart", + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "slug", + title: "Slug", + type: "slug", + description: "Genereres automatisk fra tittel", + options: { source: "title", maxLength: 96 }, + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "baseImage", + title: "Bilde", + type: "image", + options: { hotspot: true }, + description: "Dette bildet vises når en bruker ikke har oppnådd dette troféet", + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "baseDescription", + title: "Beskrivelse", + type: "text", + rows: 3, + description: "Denne teksten vises når en bruker ikke har oppnådd dette troféet", + placeholder: "F.eks. 'Bli med på gokart for å få dette troféet'", + validation: (Rule) => Rule.required(), + }), + defineField({ + name: "trophies", + title: "Legg til trofé", + type: "array", + description: + "Dersom du kunn ønsker at det skal være ett nivå, legg til ett objekt. Om du ønsker flere nivå, f.eks. gull, sølv og bronsje, kan du legge til flere objekt.", + of: [defineArrayMember({ type: "trophy" })], + options: { sortable: true }, + validation: (Rule) => Rule.min(0), + }), + ], + preview: { + select: { + title: "title", + media: "baseImage", + count: "trophies.length", + }, + prepare({ title, media, count }) { + return { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + title: title ?? "Trofé-side", + subtitle: typeof count === "number" ? `${count} trofé(er)` : "Ingen troféer enda", + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + media, + }; + }, + }, +}); diff --git a/apps/uno/bootstrap/api.go b/apps/uno/bootstrap/api.go index 636d302976..2ef8925a16 100644 --- a/apps/uno/bootstrap/api.go +++ b/apps/uno/bootstrap/api.go @@ -87,6 +87,7 @@ func RunApi() { cmsMeetingMinuteRepo := sanityinfra.NewMeetingMinuteRepo(sanityClient, logger, redisClient) cmsMovieRepo := sanityinfra.NewMovieRepo(sanityClient, logger, redisClient) cmsHSApplicationRepo := sanityinfra.NewHSApplicationRepo(sanityClient, logger, redisClient) + cmsTrophyRepo := sanityinfra.NewTrophyRepo(sanityClient, logger, redisClient) // Initialize services feideProvider := providers.NewFeideProvider(providers.FeideConfig{ @@ -130,6 +131,7 @@ func RunApi() { cmsMeetingMinuteRepo, cmsMovieRepo, cmsHSApplicationRepo, + cmsTrophyRepo, cacheInvalidator, ) diff --git a/apps/uno/domain/model/cms_trophy.go b/apps/uno/domain/model/cms_trophy.go new file mode 100644 index 0000000000..635bec7217 --- /dev/null +++ b/apps/uno/domain/model/cms_trophy.go @@ -0,0 +1,18 @@ +package model + +type CMSTrophyCollection struct { + ID string `json:"_id"` + Title string `json:"title"` + Slug string `json:"slug"` + BaseImage Image `json:"baseImage"` + BaseDescription string `json:"baseDescription"` + Trophies []CMSTrophy `json:"trophies"` +} + +type CMSTrophy struct { + Key string `json:"_key"` + Title string `json:"title"` + Description string `json:"description"` + Level string `json:"level"` + Image Image `json:"image"` +} \ No newline at end of file diff --git a/apps/uno/domain/port/cms.go b/apps/uno/domain/port/cms.go index 135cac1747..f87c37a75f 100644 --- a/apps/uno/domain/port/cms.go +++ b/apps/uno/domain/port/cms.go @@ -64,3 +64,7 @@ type CMSMovieRepo interface { type CMSHSApplicationRepo interface { GetAllHSApplications(ctx context.Context) ([]model.CMSHSApplication, error) } + +type CMSTrophyRepo interface { + GetAllTrophies(ctx context.Context) ([]model.CMSTrophy, error) +} \ No newline at end of file diff --git a/apps/uno/domain/service/cms.go b/apps/uno/domain/service/cms.go index eea8569b4d..bb57b4ef4a 100644 --- a/apps/uno/domain/service/cms.go +++ b/apps/uno/domain/service/cms.go @@ -22,6 +22,7 @@ var cmsTypeNamespaces = map[string][]string{ "merch": {sanityinfra.CMSMerchNamespaceMerch}, "meetingMinute": {sanityinfra.CMSMeetingMinuteNamespaceMeetingMinutes}, "movie": {sanityinfra.CMSMovieNamespaceMovies}, + "trophy": {sanityinfra.CMSTrophyNamespaceTrophies}, } type CMSService struct { @@ -36,6 +37,7 @@ type CMSService struct { meetingMinuteRepo port.CMSMeetingMinuteRepo movieRepo port.CMSMovieRepo hsApplicationRepo port.CMSHSApplicationRepo + trophyRepo port.CMSTrophyRepo invalidator port.CacheInvalidator } @@ -52,6 +54,7 @@ func NewCMSService( meetingMinuteRepo port.CMSMeetingMinuteRepo, movieRepo port.CMSMovieRepo, hsApplicationRepo port.CMSHSApplicationRepo, + trophyRepo port.CMSTrophyRepo, invalidator port.CacheInvalidator, ) *CMSService { return &CMSService{ @@ -65,6 +68,7 @@ func NewCMSService( merchRepo: merchRepo, meetingMinuteRepo: meetingMinuteRepo, movieRepo: movieRepo, + trophyRepo: trophyRepo, hsApplicationRepo: hsApplicationRepo, invalidator: invalidator, @@ -168,6 +172,10 @@ func (s *CMSService) GetUpcomingMovies(ctx context.Context, n int) ([]model.CMSM return s.movieRepo.GetUpcomingMovies(ctx, n) } +func (s *CMSService) GetAllTrophies(ctx context.Context) ([]model.CMSTrophy, error) { + return s.trophyRepo.GetAllTrophies(ctx) +} + // CMSHappeningFilter holds filter parameters for GetFilteredHappenings. type CMSHappeningFilter struct { Search string diff --git a/apps/uno/http/routes/api/sanity_cms.go b/apps/uno/http/routes/api/sanity_cms.go index 6a93891982..2c62909cea 100644 --- a/apps/uno/http/routes/api/sanity_cms.go +++ b/apps/uno/http/routes/api/sanity_cms.go @@ -68,6 +68,8 @@ func NewSanityMux( mux.GET("/hs-applications", s.getAllHSApplications) + mux.GET("/trophies", s.getAllTrophies) + return mux } @@ -433,6 +435,27 @@ func (s *sanityCMS) getAllMovies(ctx *handler.Context) error { return ctx.JSON(movies) } +// getAllTrophies returns all trophies from Sanity CMS +// @Summary Get all trophies from CMS +// @Tags sanity +// @Produce json +// @Success 200 {array} dto.CMSTrophyDTO "OK" +// @Failure 500 {string} string "Internal Server Error" +// @Router /sanity/trophies [get] +func (s *sanityCMS) getAllTrophies(ctx *handler.Context) error { + trophies, err := s.cmsService.GetAllTrophies(ctx.Context()) + s.logger.Error(ctx.Context(), fmt.Sprintf("error = %s", err.Error())) + s.logger.Error(ctx.Context(), fmt.Sprintf("error = %s", err.Error())) + s.logger.Error(ctx.Context(), fmt.Sprintf("error = %s", err.Error())) + s.logger.Error(ctx.Context(), fmt.Sprintf("error = %s", err.Error())) + s.logger.Error(ctx.Context(), fmt.Sprintf("error = %s", err.Error())) + + if err != nil { + return ctx.InternalServerError() + } + return ctx.JSON(trophies) +} + // getAllHSApplications returns all HS applications from Sanity CMS // @Summary Get all HS applications from CMS // @Tags sanity diff --git a/apps/uno/infrastructure/external/sanity/trophy_repo.go b/apps/uno/infrastructure/external/sanity/trophy_repo.go new file mode 100644 index 0000000000..5b24742ef9 --- /dev/null +++ b/apps/uno/infrastructure/external/sanity/trophy_repo.go @@ -0,0 +1,94 @@ +package sanityinfra + +import ( + "context" + "uno/domain/model" + "uno/domain/port" + "uno/infrastructure/cache" + "uno/pkg/sanity" + + "github.com/redis/go-redis/v9" +) + +const CMSTrophyNamespaceTrophies = "cms:trophies" + +// const trophiesBySlugQuery = ` +// *[_type == "trophies" && slug.current == $slug][0]{ +// _id, +// title, +// "slug": slug.current, +// baseImage, +// baseDescription, +// trophies[]{ +// _key, +// title, +// description, +// level, +// image +// } +// `; + +const allTrophiesQuery = ` +*[_type == "trophies"] | order(title asc) { + _id, + title, + "slug": slug.current, + baseImage, + baseDescription, + trophies[]{ + _key, + title, + description, + level, + image + }, +} +`; + +type TrophyRepo struct { + client *sanity.Client + logger port.Logger + trophyCache port.Cache[[]model.CMSTrophy] +} + +func NewTrophyRepo(client *sanity.Client, logger port.Logger, redisClient *redis.Client) port.CMSTrophyRepo { + return &TrophyRepo{ + client: client, + logger: logger, + trophyCache: cache.NewCache[[]model.CMSTrophy](redisClient, CMSTrophyNamespaceTrophies), + } +} + +// func (r *TrophyRepo) GetTrophiesBySlug(ctx context.Context, groupType string) ([]model.CMSTrophy, error) { +// r.logger.Info(ctx, "getting trophies by slug from sanity", "type", groupType) +// trophy, err := r.GetAllTrophies(ctx) +// if err != nil { +// return nil, err +// } + +// result := make([]model.CMSTrophy, 0, len(trophy)) +// for _, group := range trophy { +// if group.GroupType == groupType { +// result = append(result, group) +// } +// } +// r.logger.Info(ctx, "filtered trophies by slug from all trophies cache", "type", groupType, "count", len(result)) +// return result, nil +// } + +func (r *TrophyRepo) GetAllTrophies(ctx context.Context) ([]model.CMSTrophy, error) { + r.logger.Info(ctx, "getting all trophies from sanity") + if v, ok := r.trophyCache.Get("all"); ok { + r.logger.Info(ctx, "cache hit for all trophies") + return v, nil + } + r.logger.Info(ctx, "cache miss for all trophies") + result, err := sanity.Query[[]model.CMSTrophy](ctx, r.client, allTrophiesQuery, nil) + if err != nil { + r.logger.Error(ctx, "failed to get all Trophies from sanity", "error", err) + return nil, err + } + + r.trophyCache.Set("all", result, cmsCacheTTL) + return result, nil +} \ No newline at end of file diff --git a/apps/web/src/api/uno/client.ts b/apps/web/src/api/uno/client.ts index 078696dafd..3e07171363 100644 --- a/apps/web/src/api/uno/client.ts +++ b/apps/web/src/api/uno/client.ts @@ -618,6 +618,7 @@ export class UnoClient { quotes: QuotesApi; sanity: SanityApi; auth: AuthApi; + trophies: TrophiesApi; constructor(options: UnoClientOptions) { this.api = ky.create({ @@ -648,6 +649,7 @@ export class UnoClient { this.quotes = new QuotesApi(this); this.sanity = new SanityApi(this); this.auth = new AuthApi(this); + this.trophies = new TrophiesApi(this); } normalizePath(path: string) { @@ -1523,3 +1525,20 @@ class AuthApi { return await this.client.request("GET", `auth/magic-link/verify?${query.toString()}`); } } + +class TrophiesApi { + private client: UnoClient; + + constructor(client: UnoClient) { + this.client = client; + } + + async all() { + const response = await this.client.request("GET", "sanity/trophies"); + if (!response.ok) { + console.log("resp = ", await response.text()); + return []; + } + return await response.json(); + } +} diff --git a/apps/web/src/app/(default)/admin/layout.tsx b/apps/web/src/app/(default)/admin/layout.tsx index f011befe3d..2550ff2d7f 100644 --- a/apps/web/src/app/(default)/admin/layout.tsx +++ b/apps/web/src/app/(default)/admin/layout.tsx @@ -53,6 +53,11 @@ const adminRoutes = [ label: "Pizza", groups: ["webkom", "hovedstyret"], }, + { + href: "/admin/trofeer", + label: "Troféer", + groups: ["webkom", "hovedstyret"], + }, ]; export default async function AdminDashboardLayout({ children }: Props) { diff --git a/apps/web/src/app/(default)/admin/trofeer/page.tsx b/apps/web/src/app/(default)/admin/trofeer/page.tsx new file mode 100644 index 0000000000..f317873ec7 --- /dev/null +++ b/apps/web/src/app/(default)/admin/trofeer/page.tsx @@ -0,0 +1,53 @@ +import Image from "next/image"; + +import { Container } from "@/components/container"; +import { Heading } from "@/components/typography/heading"; +import { Text } from "@/components/typography/text"; +import { ensureWebkomOrHovedstyret } from "@/lib/ensure"; +import { UserTrophiesModal } from "./usertrophies-modal"; +import { unoWithAdmin } from "../../../../api/server"; +import { urlFor } from "../../../../lib/sanity"; + +export default async function TrophyPage() { + await ensureWebkomOrHovedstyret(); + + const trophies = await Promise.all([unoWithAdmin.trophies.all()]); + const [users] = await Promise.all([unoWithAdmin.users.all()]); + + if (!trophies) { + return

Kunne ikke hente troféer.

; + } + + return ( + + Troféer + Her er det mulig å redigere hvem som fortjener alle troféene. + + {trophies.map((trophy) => ( +
+

{trophy.title}

+
+ {trophy.trophies && ( +
+ {trophy.trophies.map((t) => ( +
+ Empty trophy + +
+ ))} +
+ )} +
+
+ ))} +
+ ); +} diff --git a/apps/web/src/app/(default)/admin/trofeer/usertrophies-modal.tsx b/apps/web/src/app/(default)/admin/trofeer/usertrophies-modal.tsx new file mode 100644 index 0000000000..04585e11a6 --- /dev/null +++ b/apps/web/src/app/(default)/admin/trofeer/usertrophies-modal.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { useState } from "react"; +import { DialogClose } from "@radix-ui/react-dialog"; +import { CheckIcon } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { type AllUsers } from "../brukere/page"; + +export const UserTrophiesModal = ({ users }: { users: AllUsers }) => { + const [searchQuery, setSearchQuery] = useState(""); + return ( + + + Rediger personer + + + + Personer + + + +
+ + { + setSearchQuery(e.target.value); + }} + placeholder="Søk..." + /> +
+
+ {users.map((user) => ( +
+ +
+ ))} +
+
+ + + + + +
+
+ ); +}; + +type User = AllUsers[number]; + +const LineForGivingTrophyToUser = ({ user }: { user: User }) => { + return ( +
+

{user.name}

+ +
+ ); +}; + +const GiveTrophyButton = ({ hasTrophy }: { hasTrophy: boolean }) => { + return ( +
+ {hasTrophy ? :

Gi

} +
+ ); +}; diff --git a/apps/web/src/app/(default)/auth/profil/layout.tsx b/apps/web/src/app/(default)/auth/profil/layout.tsx new file mode 100644 index 0000000000..80c93656ce --- /dev/null +++ b/apps/web/src/app/(default)/auth/profil/layout.tsx @@ -0,0 +1,38 @@ +import { + Sidebar, + SidebarItem, + SidebarLayoutContent, + SidebarLayoutRoot, +} from "@/components/sidebar-layout"; + +const routes = [ + { + label: "Profil", + href: "/auth/profil", + }, + { + label: "Arrangementer", + href: "/auth/profil/arrangementer", + }, + { + label: "Troféer", + href: "/auth/profil/trofeer", + }, +]; + +export default function ProfileLayout({ children }: { children: React.ReactNode }) { + return ( + + + {routes.map((route) => { + return ( + + {route.label} + + ); + })} + + {children} + + ); +} diff --git a/apps/web/src/app/(default)/auth/user/[id]/layout.tsx b/apps/web/src/app/(default)/auth/user/[id]/layout.tsx index 4f88f64b23..d3ea4f855f 100644 --- a/apps/web/src/app/(default)/auth/user/[id]/layout.tsx +++ b/apps/web/src/app/(default)/auth/user/[id]/layout.tsx @@ -16,6 +16,10 @@ const getRoutes = (profileOwnerId: string) => { label: "Arrangementer", href: `/auth/user/${profileOwnerId}/arrangementer`, }, + { + label: "Troféer", + href: `/auth/user/${profileOwnerId}/trofeer`, + }, ]; }; diff --git a/apps/web/src/app/(default)/auth/user/[id]/trofeer/page.tsx b/apps/web/src/app/(default)/auth/user/[id]/trofeer/page.tsx new file mode 100644 index 0000000000..e5057635ce --- /dev/null +++ b/apps/web/src/app/(default)/auth/user/[id]/trofeer/page.tsx @@ -0,0 +1,60 @@ +import Image from "next/image"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth/session"; +import { Heading } from "@/components/typography/heading"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { urlFor } from "../../../../../../lib/sanity"; +import { unoWithAdmin } from "../../../../../../api/server"; + +export default async function UserTrophies() { + const user = await auth(); + + if (!user) { + return redirect("/auth/logg-inn"); + } + + const trophies = await Promise.all([unoWithAdmin.trophies.all()]); + + if (!trophies) { + return

Kunne ikke hente troféer.

; + } + + return ( +
+ + Dine troféer + +

Denne siden er under produksjon ...

+
+ {trophies.map((trophy) => ( + + + Empty trophy + {trophy.title} + + + Empty trophy +

{trophy.title}

+

{trophy.baseDescription}

+
+
+ ))} +
+
+ ); +} diff --git a/apps/web/src/app/(default)/auth/user/[id]/trofeer/trophy-helpers.ts b/apps/web/src/app/(default)/auth/user/[id]/trofeer/trophy-helpers.ts new file mode 100644 index 0000000000..eba35b7482 --- /dev/null +++ b/apps/web/src/app/(default)/auth/user/[id]/trofeer/trophy-helpers.ts @@ -0,0 +1,97 @@ +import { + BedriftsturEmpty, + BedriftsturGold, + BrannkampBronze, + BrannkampEmpty, + BrannkampGold, + BrannkampSilver, + GokartBronze, + GokartEmpty, + GokartGold, + GokartSilver, + VinterballBronze, + VinterballEmpty, + VinterballGold, + VinterballSilver, +} from "@/assets/images/trophies"; +import { getRegistrationsForTrophy, type RegistrationForTrophy } from "@/data/trophy/queries"; +import { TrophyName } from "./page"; + +export const getUserTrophies = async (userId: string) => { + const registrations = await getRegistrationsForTrophy(userId); + const gokart = getGoKartTrophy(registrations); + const bedriftstur = getBedriftsturTrophy(registrations); + return [gokart, bedriftstur]; +}; + +export const getGoKartTrophy = (registrations: Array) => { + const gokartRegistrations = registrations.filter((registration) => + registration.happening.slug.includes("gokart"), + ); + const name = TrophyName.GOKART; + const level = + gokartRegistrations.length >= 3 + ? TrophyName.GOLD + : gokartRegistrations.length === 2 + ? TrophyName.SILVER + : gokartRegistrations.length === 1 + ? TrophyName.BRONZE + : TrophyName.EMPTY; + + const image = + gokartRegistrations.length >= 3 + ? GokartGold + : gokartRegistrations.length === 2 + ? GokartSilver + : gokartRegistrations.length === 1 + ? GokartBronze + : GokartEmpty; + + const title = + gokartRegistrations.length >= 3 + ? "Formel 1 sjåfør" + : gokartRegistrations.length === 2 + ? "Oppkjøring" + : gokartRegistrations.length === 1 + ? "Kjøretime" + : "Ikke lappen"; + const description = + gokartRegistrations.length >= 3 + ? "Du har deltatt på tre gokart arrangement.\n\nDu er jammen meg glad i Gokart!" + : gokartRegistrations.length === 2 + ? "Du har deltatt på to gokart arrangement.\n\nDelta på én til og få gull Gokart!" + : gokartRegistrations.length === 1 + ? "Du har deltatt på ett gokart arrangement.\n\nDelta på flere og få gull og bronse!" + : "Delta på Gokart arrangement med Gnist for å oppnå dette trofeet!"; + return { + name, + level, + image, + title, + description, + }; +}; + +export const getBedriftsturTrophy = (registrations: Array) => { + const bedriftsturRegistrations = registrations.filter((registration) => + registration.happening.slug.includes("bedriftstur"), + ); + + const isGold = bedriftsturRegistrations.length >= 1; + + const name = TrophyName.BEDRIFTSTUR; + const level = isGold ? TrophyName.GOLD : TrophyName.EMPTY; + const image = isGold ? BedriftsturGold : BedriftsturEmpty; + const title = isGold ? "Bedriftstur-veteran" : "Ikke deltatt"; + const description = isGold + ? "Du har deltatt på bedriftstur med Gnist!\n\nHåper du hadde en fin tur!" + : "Delta på bedriftstur med Gnist for å oppnå dette trofeet!"; + + return { + name, + level, + image, + title, + description, + }; +}; diff --git a/apps/web/src/data/trophy/queries.ts b/apps/web/src/data/trophy/queries.ts new file mode 100644 index 0000000000..5b85648e56 --- /dev/null +++ b/apps/web/src/data/trophy/queries.ts @@ -0,0 +1,24 @@ +// import { getRegistrationsByUserId } from "../registrations/queries"; + +// export type RegistrationForTrophy = { +// userId: string; +// status: "registered" | "unregistered" | "removed" | "waiting" | "pending"; +// happening: { +// slug: string; +// title: string; +// }; +// }; + +// export async function getRegistrationsForTrophy( +// userId: string, +// ): Promise> { +// const rows = await getRegistrationsByUserId(userId); +// return rows.map((r) => ({ +// userId: r.userId, +// status: r.status, +// happening: { +// slug: r.happening.slug, +// title: r.happening.title, +// }, +// })); +// } diff --git a/packages/db/src/schemas/user-trophies.ts b/packages/db/src/schemas/user-trophies.ts new file mode 100644 index 0000000000..5f03544da5 --- /dev/null +++ b/packages/db/src/schemas/user-trophies.ts @@ -0,0 +1,32 @@ +import { type InferInsertModel, type InferSelectModel } from "drizzle-orm"; +import { + boolean, + pgTable, + primaryKey, + text, + varchar, + integer, +} from "drizzle-orm/pg-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; + +import { users } from "./users"; + +export const usersTrophies = pgTable( + "users_to_groups", + { + userId: text("user_id") + .notNull() + .references(() => users.id, { + onDelete: "cascade", + }), + trophyId: varchar("group_id", { length: 255 }).notNull(), + level: integer("trophy_level").notNull(), + }, + (t) => [primaryKey({ columns: [t.userId, t.trophyId] })], +).enableRLS(); + +export type UsersTrophies = InferSelectModel; +export type UsersTrophiesInsert = InferInsertModel; + +export const selectUsersTrophiesSchema = createSelectSchema(usersTrophies); +export const insertUsersTrophiesSchema = createInsertSchema(usersTrophies); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af26db9039..77088a1854 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2728,183 +2728,155 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm64@1.2.4': resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -3197,56 +3169,48 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-gnu@16.2.0': resolution: {integrity: sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-arm64-musl@16.2.0': resolution: {integrity: sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-gnu@16.2.0': resolution: {integrity: sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-linux-x64-musl@16.2.0': resolution: {integrity: sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -3406,56 +3370,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.43.0': resolution: {integrity: sha512-ROaWfYh+6BSJ1Arwy5ujijTlwnZetxDxzBpDc1oBR4d7rfrPBqzeyjd5WOudowzQUgyavl2wEpzn1hw3jWcqLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.43.0': resolution: {integrity: sha512-PJRs/uNxmFipJJ8+SyKHh7Y7VZIKQicqrrBzvfyM5CtKi8D7yZKTwUOZV3ffxmiC2e7l1SDJpkBEOyue5NAFsg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.43.0': resolution: {integrity: sha512-j6biGAgzIhj+EtHXlbNumvwG7XqOIdiU4KgIWRXAEj/iUbHKukKW8eXa4MIwpQwW1YkxovduKtzEAPnjlnAhVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.43.0': resolution: {integrity: sha512-RYWxAcslKxvy7yri24Xm9cmD0RiANaiEPs007EFG6l9h1ChM69Q5SOzACaCoz4Z9dEplnhhneeBaTWMEdpgIbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.43.0': resolution: {integrity: sha512-DT6Q8zfQQy3jxpezAsBACEHNUUixKSYTwdXeXojNHe4DQOoxjPdjr3Szu6BRNjxLykZM/xMNmp9ElOIyDppwtw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.43.0': resolution: {integrity: sha512-R8Yk7iYcuZORXmCfFZClqbDxRZgZ9/HEidUuBNdoX8Ptx07cMePnMVJ/woB84lFIDjh2ROHVaOP40Ds3rBXFqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.43.0': resolution: {integrity: sha512-F2YYqyvnQNvi320RWZNAvsaWEHwmW3k4OwNJ1hZxRKXupY63expbBaNp6jAgvYs7y/g546vuQnGHQuCBhslhLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/binding-openharmony-arm64@0.43.0': resolution: {integrity: sha512-OE6TdietLXV3F6c7pNIhx/9YC1/2YFwjU9DPc/fbjxIX19hNIaP1rS0cFjCGJlGX+cVJwIKWe8Mos+LdQ1yAJw==} @@ -3528,56 +3484,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.58.0': resolution: {integrity: sha512-zSoYRo5dxHLcUx93Stl2hW3hSNjPt99O70eRVWt5A1zwJ+FPjeCCANCD2a9R4JbHsdcl11TIQOjyigcRVOH2mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.58.0': resolution: {integrity: sha512-NQ0U/lqxH2/VxBYeAIvMNUK1y0a1bJ3ZicqkF2c6wfakbEciP9jvIE4yNzCFpZaqeIeRYaV7AVGqEO1yrfVPjA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.58.0': resolution: {integrity: sha512-X9J+kr3gIC9FT8GuZt0ekzpNUtkBVzMVU4KiKDSlocyQuEgi3gBbXYN8UkQiV77FTusLDPsovjo95YedHr+3yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.58.0': resolution: {integrity: sha512-CDze3pi1OO3Wvb/QsXjmLEY4XPKGM6kIo82ssNOgmcl1IdndF9VSGAE38YLhADWmOac7fjqhBw82LozuUVxD0Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.58.0': resolution: {integrity: sha512-b/89glbxFaEAcA6Uf1FvCNecBJEgcUTsV1quzrqXM/o4R1M4u+2KCVuyGCayN2UpsRWtGGLb+Ver0tBBpxaPog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.58.0': resolution: {integrity: sha512-0/yYpkq9VJFCEcuRlrViGj8pJUFFvNS4EkEREaN7CB1EcLXJIaVSSa5eCihwBGXtOZxhnblWgxks9juRdNQI7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.58.0': resolution: {integrity: sha512-hr6FNvmcAXiH+JxSvaJ4SJ1HofkdqEElXICW9sm3/Rd5eC3t7kzvmLyRAB3NngKO2wzXRCAm4Z/mGWfrsS4X8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.58.0': resolution: {integrity: sha512-R+O368VXgRql1K6Xar+FEo7NEwfo13EibPMoTv3sesYQedRXd6m30Dh/7lZMxnrQVFfeo4EOfYIP4FpcgWQNHg==} @@ -4711,157 +4659,131 @@ packages: resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.57.1': resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.56.0': resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.56.0': resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.56.0': resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.56.0': resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.56.0': resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.56.0': resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.56.0': resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.56.0': resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.56.0': resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.56.0': resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.56.0': resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.56.0': resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.56.0': resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==} @@ -5335,28 +5257,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -6077,7 +5995,7 @@ packages: basic-ftp@5.1.0: resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} engines: {node: '>=10.0.0'} - deprecated: Security vulnerability fixed in 5.2.1, please upgrade + deprecated: Security vulnerability fixed in 5.2.0, please upgrade bcp-47-match@2.0.3: resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} @@ -8074,28 +7992,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}