diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml
index beb4ebd8..c1dc2a4b 100644
--- a/.github/workflows/deploy-dev.yml
+++ b/.github/workflows/deploy-dev.yml
@@ -22,6 +22,14 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
+ - name: npm install
+ run: |
+ npm i
+ - name: Generate Tailwind CSS
+ # 🚨 Must come before `templ_generate` to ensure CSS hash in
+ # layout.templ is updated first, before go templates compile
+ run: |
+ npm run tailwind:prod
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
diff --git a/.github/workflows/deploy-feature.yml b/.github/workflows/deploy-feature.yml
index a50c1490..574b71e2 100644
--- a/.github/workflows/deploy-feature.yml
+++ b/.github/workflows/deploy-feature.yml
@@ -28,6 +28,14 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
+ - name: npm install
+ run: |
+ npm i
+ - name: Generate Tailwind CSS
+ # 🚨 Must come before `templ_generate` to ensure CSS hash in
+ # layout.templ is updated first, before go templates compile
+ run: |
+ npm run tailwind:prod
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
@@ -78,7 +86,7 @@ jobs:
uses: './.github/actions/generate_cloudflare_locations_file'
- name: Deploy AWS resources via SST
run: |
- npm i && npx sst deploy --stage ${{ steps.extract_branch.outputs.branch }}
+ npx sst deploy --stage ${{ steps.extract_branch.outputs.branch }}
- name: Deploy to Cloudflare Workers with Wrangler
uses: cloudflare/wrangler-action@v3.7.0
env:
diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml
index c096d5df..ab9b05f0 100644
--- a/.github/workflows/deploy-prod.yml
+++ b/.github/workflows/deploy-prod.yml
@@ -22,6 +22,14 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: '1.21.x'
+ - name: npm install
+ run: |
+ npm i
+ - name: Generate Tailwind CSS
+ # 🚨 Must come before `templ_generate` to ensure CSS hash in
+ # layout.templ is updated first, before go templates compile
+ run: |
+ npm run tailwind:prod
- name: Generate templ code
uses: './.github/actions/templ_generate'
with:
diff --git a/API_ENDPOINTS.md b/API_ENDPOINTS.md
index 02dd7d2b..7eb5963d 100644
--- a/API_ENDPOINTS.md
+++ b/API_ENDPOINTS.md
@@ -26,7 +26,7 @@ curl -X POST https://devnear.me/api/users \
"role": "standard_user"
}'
-
+
```
2. Get User by ID
@@ -62,7 +62,7 @@ curl -X PUT https://devnear.me/api/users/<:user_id> \
4. Delete User
```bash
-curl -X DELETE https://devnear.me/api/users/<:user_id>
+curl -X DELETE https://devnear.me/api/users/<:user_id>
```
## Purchasables
@@ -161,7 +161,7 @@ curl -X POST https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/event-rs
}'
```
-2. GET EventRsvp By PK
+2. GET EventRsvp By PK
```bash
curl -X GET https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/event-rsvps/6ce1be30-f700-475c-b84a-49af0c73f337/ea49a5f8-e27c-47b0-8237-6f6f380a048c \
-H "Content-Type: application/json"
@@ -279,57 +279,5 @@ curl -X DELETE https://devnear.me/api/registration-fields/<:event_id> \
```
-## Registrations
-
-#### Note eventId comes before userId in url params
-
-1. Create Registration
-```bash
-curl -X POST https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/registrations/62352e94-b34d-4ee7-a9d1-f1c8e404dec0/99413f71-bb0e-43c9-bc3a-fafc64c5c799 \
- -H "Content-Type: application/json" \
- -d '{
- "responses": [
- {"attendeeEmail": "me@meetnear.ne"},
- {"tShirtSize": "XL"}
- ]
- }'
-```
-2. Get Registration by Primary Key
-```bash
-/api/registrations/{:event_id}/{:user_id}
-curl -X GET https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/registrations/62352e94-b34d-4ee7-a9d1-f1c8e404dec0/c9413f71-bb0e-43c9-bc3a-fafc64c5c799 \
- -H "Content-Type: application/json"
-```
-
-3. Get Registration by EventId
-```bash
-curl -X GET https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/registrations/event/62352e94-b34d-4ee7-a9d1-f1c8e404dec0 \
- -H "Content-Type: application/json"
-```
-
-4. Get Registration by UserId
-```bash
-curl -X GET https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/registrations/user/c9413f71-bb0e-43c9-bc3a-fafc64c5c799 \
- -H "Content-Type: application/json"
-```
-
-5. Update registration (uses PK)
-```bash
-curl -X PUT https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/registrations/62352e94-b34d-4ee7-a9d1-f1c8e404dec0/c9413f71-bb0e-43c9-bc3a-fafc64c5c799 \
- -H "Content-Type: application/json" \
- -d '{
- "responses": [
- {"attendeeEmail": "newemail@meetnear.ne"},
- {"tShirtSize": "L"}
- ]
- }'
-```
-
-6. Delete Registration (uses PK)
-```bash
-curl -X DELETE https://v63ojpt121.execute-api.us-east-1.amazonaws.com/api/registrations/62352e94-b34d-4ee7-a9d1-f1c8e404dec0/c9413f71-bb0e-43c9-bc3a-fafc64c5c799 \
- -H "Content-Type: application/json"
-```
-
diff --git a/functions/gateway/handlers/data_handlers.go b/functions/gateway/handlers/data_handlers.go
index cce830ea..36a45b2d 100644
--- a/functions/gateway/handlers/data_handlers.go
+++ b/functions/gateway/handlers/data_handlers.go
@@ -79,6 +79,12 @@ type rawEvent struct {
HideCrossPromo *bool `json:"hideCrossPromo,omitempty"`
}
+// Create a new struct that includes the createPurchase fields and the Stripe checkout URL
+type PurchaseResponse struct {
+ internal_types.PurchaseInsert
+ StripeCheckoutURL string `json:"stripe_checkout_url"`
+}
+
func ConvertRawEventToEvent(raw rawEvent, requireId bool) (types.Event, error) {
loc, err := time.LoadLocation(raw.Timezone)
if err != nil {
@@ -634,7 +640,9 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
}
// all purchases are pending and a client passing this status should be overridden
- createPurchase.Status = "PENDING"
+ if createPurchase.Status == "" {
+ createPurchase.Status = "PENDING"
+ }
err = json.Unmarshal(body, &createPurchase)
if err != nil {
@@ -655,12 +663,51 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
createdAtString := fmt.Sprintf("%020d", _createdAt) // Pad with zeros to a fixed width of 20 digits
createPurchase.CreatedAtString = createdAtString
+ referenceId := "event-" + eventId + "-user-" + userId + "-time-" + createPurchase.CreatedAtString
+
+ // Create the composite key
+ compositeKey := fmt.Sprintf("%s_%s_%s", createPurchase.EventID, createPurchase.UserID, createPurchase.CreatedAtString)
+
+ // Add the composite key and createdAt to the purchase object
+ createPurchase.CompositeKey = compositeKey
+
+ // Create the purchase record immediately instead of deferring it
+ purchaseService := dynamodb_service.NewPurchaseService()
+ purchaseHandler := dynamodb_handlers.NewPurchaseHandler(purchaseService)
+
+ // when there are no purchased items, we treat this as an "RSVP" or "INTERESTED" status that shows
+ // in the users purchase / registration history. The empty PurchasedItems array signals that this
+ // is an event that does not have `RegistrationFields` or `PurchasableItems`
+ if len(createPurchase.PurchasedItems) == 0 {
+ db := transport.GetDB()
+ log.Printf("createPurchase: %+v", createPurchase)
+ _, err := purchaseHandler.PurchaseService.InsertPurchase(r.Context(), db, createPurchase)
+ if err != nil {
+ transport.SendServerRes(w, []byte("Failed to insert free purchase into database: "+err.Error()), http.StatusInternalServerError, err)
+ return err
+ }
+
+ // Create the response object
+ response := PurchaseResponse{
+ PurchaseInsert: createPurchase,
+ StripeCheckoutURL: "", // Empty URL for free items
+ }
+
+ // Marshal and send the response
+ purchaseJSON, err := json.Marshal(response)
+ if err != nil {
+ transport.SendServerRes(w, []byte("Failed to marshal purchase response: "+err.Error()), http.StatusInternalServerError, err)
+ return err
+ }
+ transport.SendServerRes(w, purchaseJSON, http.StatusOK, nil)
+ return nil
+ }
purchasableService := dynamodb_service.NewPurchasableService()
- h := dynamodb_handlers.NewPurchasableHandler(purchasableService)
+ purchasableHandler := dynamodb_handlers.NewPurchasableHandler(purchasableService)
db := transport.GetDB()
- purchasable, err := h.PurchasableService.GetPurchasablesByEventID(r.Context(), db, eventId)
+ purchasable, err := purchasableHandler.PurchasableService.GetPurchasablesByEventID(r.Context(), db, eventId)
if err != nil {
transport.SendServerRes(w, []byte("Failed to get purchasables for event id: "+eventId+" "+err.Error()), http.StatusInternalServerError, err)
return
@@ -683,13 +730,11 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
}
}
- // this boolean gets toggled in the scenario where stripe
- // checkout instantiation or other unrelated checkout steps
- // AFTER the inventory is officially "held" + optimistically
- // decremented
+ // this boolean gets toggled in the scenario where stripe checkout fails to complete or other
+ // unrelated checkout failures AFTER the inventory is officially "held" + optimistically decremented
var needsRevert bool
- err = h.PurchasableService.UpdatePurchasableInventory(r.Context(), db, eventId, inventoryUpdates, purchasableMap)
+ err = purchasableHandler.PurchasableService.UpdatePurchasableInventory(r.Context(), db, eventId, inventoryUpdates, purchasableMap)
if err != nil {
transport.SendServerRes(w, []byte("Failed to update inventory: "+err.Error()), http.StatusInternalServerError, err)
return
@@ -706,13 +751,46 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
PurchasableIndex: update.PurchasableIndex,
}
}
- revertErr := h.PurchasableService.UpdatePurchasableInventory(r.Context(), db, eventId, revertUpdates, purchasableMap)
+ revertErr := purchasableHandler.PurchasableService.UpdatePurchasableInventory(r.Context(), db, eventId, revertUpdates, purchasableMap)
if revertErr != nil {
log.Printf("ERR: Failed to revert inventory changes: %v", revertErr)
}
}
}()
+ // Handle for free item purchases. These still need to track inventory and update the database, though we don't
+ // need to create a Stripe checkout session
+ if createPurchase.Total == 0 {
+ // Skip Stripe checkout for free items
+ createPurchase.Status = helpers.PurchaseStatus.Registered // Mark as registered immediately since it's free
+
+ db := transport.GetDB()
+ log.Printf("createPurchase: %+v", createPurchase)
+ _, err := purchaseHandler.PurchaseService.InsertPurchase(r.Context(), db, createPurchase)
+ if err != nil {
+ needsRevert = true
+ transport.SendServerRes(w, []byte("Failed to insert free purchase into database: "+err.Error()), http.StatusInternalServerError, err)
+ return err
+ }
+
+ // Create the response object
+ response := PurchaseResponse{
+ PurchaseInsert: createPurchase,
+ StripeCheckoutURL: "", // Empty URL for free items
+ }
+
+ // Marshal and send the response
+ purchaseJSON, err := json.Marshal(response)
+ if err != nil {
+ transport.SendServerRes(w, []byte("Failed to marshal purchase response: "+err.Error()), http.StatusInternalServerError, err)
+ return err
+ }
+ log.Printf("purchaseJSON: %s", string(purchaseJSON)) // Conve
+ transport.SendServerRes(w, purchaseJSON, http.StatusOK, nil)
+ return nil
+ }
+
+ // Continue with existing Stripe checkout logic for paid items
_, stripePrivKey := services.GetStripeKeyPair()
stripe.Key = stripePrivKey
@@ -736,10 +814,9 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
}
}
- referenceId := "event-" + eventId + "-user-" + userId + "-time-" + createPurchase.CreatedAtString
params := &stripe.CheckoutSessionParams{
ClientReferenceID: stripe.String(referenceId), // Store purchase
- SuccessURL: stripe.String(os.Getenv("APEX_URL") + "/event/" + eventId + "?checkout=success"),
+ SuccessURL: stripe.String(os.Getenv("APEX_URL") + "/admin/profile?new_purch_key=" + createPurchase.CompositeKey),
CancelURL: stripe.String(os.Getenv("APEX_URL") + "/event/" + eventId + "?checkout=cancel"),
LineItems: lineItems,
// NOTE: `mode` needs to be "subscription" if there's a subscription / recurring item,
@@ -764,19 +841,11 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
// Now that the checks are in place, we defer the transaction creation in the database
// to respond to the client as quickly as possible
defer func() {
- purchaseService := dynamodb_service.NewPurchaseService()
- h := dynamodb_handlers.NewPurchaseHandler(purchaseService)
- createPurchase.Status = helpers.StripeCheckoutStatus.Pending
-
- // Create the composite key
- compositeKey := fmt.Sprintf("%s_%s_%s", createPurchase.EventID, createPurchase.UserID, createPurchase.CreatedAtString)
-
- // Add the composite key and createdAt to the purchase object
- createPurchase.CompositeKey = compositeKey
+ createPurchase.Status = helpers.PurchaseStatus.Pending
log.Printf("db payload `createPurchase`: %+v", createPurchase)
db := transport.GetDB()
- _, err := h.PurchaseService.InsertPurchase(r.Context(), db, createPurchase)
+ _, err := purchaseHandler.PurchaseService.InsertPurchase(r.Context(), db, createPurchase)
if err != nil {
log.Printf("ERR: failed to insert purchase into purchases database for stripe session ID: %+v, err: %+v", stripeCheckoutResult.ID, err)
}
@@ -784,12 +853,6 @@ func CreateCheckoutSession(w http.ResponseWriter, r *http.Request) (err error) {
log.Printf("\nstripe result: %+v", stripeCheckoutResult)
- // Create a new struct that includes the createPurchase fields and the Stripe checkout URL
- type PurchaseResponse struct {
- internal_types.PurchaseInsert
- StripeCheckoutURL string `json:"stripe_checkout_url"`
- }
-
// Create the response object
response := PurchaseResponse{
PurchaseInsert: createPurchase,
@@ -895,7 +958,7 @@ func (h *PurchasableWebhookHandler) HandleCheckoutWebhook(w http.ResponseWriter,
return err
}
purchaseUpdate := TransformPurchaseToUpdate(*purchase)
- purchaseUpdate.Status = helpers.StripeCheckoutStatus.Settled
+ purchaseUpdate.Status = helpers.PurchaseStatus.Settled
if checkoutSession.PaymentIntent != nil {
purchaseUpdate.StripeTransactionId = checkoutSession.PaymentIntent.ID
}
@@ -980,7 +1043,7 @@ func (h *PurchasableWebhookHandler) HandleCheckoutWebhook(w http.ResponseWriter,
return err
}
purchaseUpdate := TransformPurchaseToUpdate(*purchase)
- purchaseUpdate.Status = helpers.StripeCheckoutStatus.Canceled
+ purchaseUpdate.Status = helpers.PurchaseStatus.Canceled
_, err = h.PurchaseService.UpdatePurchase(r.Context(), db, eventID, userID, purchase.CreatedAtString, purchaseUpdate)
if err != nil {
diff --git a/functions/gateway/handlers/data_handlers_test.go b/functions/gateway/handlers/data_handlers_test.go
index 9d6f5e73..2c75d8a6 100644
--- a/functions/gateway/handlers/data_handlers_test.go
+++ b/functions/gateway/handlers/data_handlers_test.go
@@ -1171,7 +1171,7 @@ func TestHandleCheckoutWebhook(t *testing.T) {
EventID: eventId,
UserID: userId,
CreatedAtString: createdAt,
- Status: helpers.StripeCheckoutStatus.Pending,
+ Status: helpers.PurchaseStatus.Pending,
PurchasedItems: []internal_types.PurchasedItem{
{
Name: "Test Item",
@@ -1182,8 +1182,8 @@ func TestHandleCheckoutWebhook(t *testing.T) {
}, nil
},
UpdatePurchaseFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId, createdAt string, update internal_types.PurchaseUpdate) (*internal_types.Purchase, error) {
- if update.Status != helpers.StripeCheckoutStatus.Settled {
- t.Errorf("expected status %v, got %v", helpers.StripeCheckoutStatus.Settled, update.Status)
+ if update.Status != helpers.PurchaseStatus.Settled {
+ t.Errorf("expected status %v, got %v", helpers.PurchaseStatus.Settled, update.Status)
}
return nil, nil
},
@@ -1357,7 +1357,7 @@ func TestHandleCheckoutWebhook(t *testing.T) {
EventID: eventId,
UserID: userId,
CreatedAtString: createdAt,
- Status: helpers.StripeCheckoutStatus.Pending,
+ Status: helpers.PurchaseStatus.Pending,
PurchasedItems: []internal_types.PurchasedItem{
{
Name: "Test Item",
@@ -1368,8 +1368,8 @@ func TestHandleCheckoutWebhook(t *testing.T) {
}, nil
},
UpdatePurchaseFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId, createdAt string, update internal_types.PurchaseUpdate) (*internal_types.Purchase, error) {
- if update.Status != helpers.StripeCheckoutStatus.Canceled {
- t.Errorf("expected status %v, got %v", helpers.StripeCheckoutStatus.Canceled, update.Status)
+ if update.Status != helpers.PurchaseStatus.Canceled {
+ t.Errorf("expected status %v, got %v", helpers.PurchaseStatus.Canceled, update.Status)
}
return nil, nil
},
diff --git a/functions/gateway/handlers/dynamodb_handlers/event_rsvp_handlers.go b/functions/gateway/handlers/dynamodb_handlers/event_rsvp_handlers.go
deleted file mode 100644
index fd910660..00000000
--- a/functions/gateway/handlers/dynamodb_handlers/event_rsvp_handlers.go
+++ /dev/null
@@ -1,287 +0,0 @@
-package dynamodb_handlers
-
-import (
- "encoding/json"
- "io"
- "net/http"
- "time"
-
- "github.com/gorilla/mux"
- dynamodb_service "github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
- "github.com/meetnearme/api/functions/gateway/transport"
- internal_types "github.com/meetnearme/api/functions/gateway/types"
-)
-
-type EventRsvpHandler struct {
- EventRsvpService internal_types.EventRsvpServiceInterface
-}
-
-func NewEventRsvpHandler(eventRsvpService internal_types.EventRsvpServiceInterface) *EventRsvpHandler {
- return &EventRsvpHandler{EventRsvpService: eventRsvpService}
-}
-
-
-func (h *EventRsvpHandler) CreateEventRsvp(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- var createEventRsvp internal_types.EventRsvpInsert
- body, err := io.ReadAll(r.Body)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to read request body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- err = json.Unmarshal(body, &createEventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid JSON payload: "+err.Error()), http.StatusUnprocessableEntity, err)
- return
- }
-
- createEventRsvp.CreatedAt = time.Now()
- createEventRsvp.UpdatedAt = time.Now()
- createEventRsvp.EventID = eventId
- createEventRsvp.UserID = userId
-
- err = validate.Struct(&createEventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- db := transport.GetDB()
- res, err := h.EventRsvpService.InsertEventRsvp(r.Context(), db, createEventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to create eventRsvp: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- response, err := json.Marshal(res)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusCreated, nil)
-}
-
-func (h *EventRsvpHandler) GetEventRsvpByPk(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing eventRsvp ID"), http.StatusBadRequest, nil)
- return
- }
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing eventRsvp ID"), http.StatusBadRequest, nil)
- return
- }
-
- db := transport.GetDB()
- eventRsvp, err := h.EventRsvpService.GetEventRsvpByPk(r.Context(), db, eventId, userId)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get user: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- if eventRsvp == nil {
- transport.SendServerRes(w, []byte("EventRsvp not found"), http.StatusNotFound, nil)
- return
- }
-
- response, err := json.Marshal(eventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *EventRsvpHandler) GetEventRsvpsByUserID(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- id := vars["user_id"]
- if id == "" {
- transport.SendServerRes(w, []byte("Missing user_id ID"), http.StatusBadRequest, nil)
- return
- }
-
- db := transport.GetDB()
- users, err := h.EventRsvpService.GetEventRsvpsByUserID(r.Context(), db, id)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get user's eventRsvps: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- response, err := json.Marshal(users)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *EventRsvpHandler) GetEventRsvpsByEventID(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- id := vars["event_id"]
- if id == "" {
- transport.SendServerRes(w, []byte("Missing event_id ID"), http.StatusBadRequest, nil)
- return
- }
-
- db := transport.GetDB()
- events, err := h.EventRsvpService.GetEventRsvpsByEventID(r.Context(), db, id)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get user's eventRsvps: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- response, err := json.Marshal(events)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *EventRsvpHandler) UpdateEventRsvp(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing eventRsvp ID"), http.StatusBadRequest, nil)
- return
- }
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing eventRsvp ID"), http.StatusBadRequest, nil)
- return
- }
-
- var updateEventRsvp internal_types.EventRsvpUpdate
- body, err := io.ReadAll(r.Body)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to read request body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- err = json.Unmarshal(body, &updateEventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid JSON payload: "+err.Error()), http.StatusUnprocessableEntity, err)
- return
- }
-
- err = validate.Struct(&updateEventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- db := transport.GetDB()
- user, err := h.EventRsvpService.UpdateEventRsvp(r.Context(), db, eventId, userId, updateEventRsvp)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to update eventRsvp: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- if user == nil {
- transport.SendServerRes(w, []byte("EventRsvp not found"), http.StatusNotFound, nil)
- return
- }
-
- response, err := json.Marshal(user)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *EventRsvpHandler) DeleteEventRsvp(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- db := transport.GetDB()
- err := h.EventRsvpService.DeleteEventRsvp(r.Context(), db, eventId, userId)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to delete eventRsvp: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, []byte("EventRsvp successfully deleted"), http.StatusOK, nil)
-}
-
-func CreateEventRsvpHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- eventRsvpService := dynamodb_service.NewEventRsvpService()
- handler := NewEventRsvpHandler(eventRsvpService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.CreateEventRsvp(w, r)
- }
-}
-
-
-// GetEventRsvpHandler is a wrapper that creates the UserHandler and returns the handler function for getting a eventRsvp by ID
-func GetEventRsvpByPkHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- eventRsvpService := dynamodb_service.NewEventRsvpService()
- handler := NewEventRsvpHandler(eventRsvpService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.GetEventRsvpByPk(w, r)
- }
-}
-
-// GetEventRsvpsHandler is a wrapper that creates the UserHandler and returns the handler function for getting all eventRsvps
-func GetEventRsvpsByEventIDHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- eventRsvpService := dynamodb_service.NewEventRsvpService()
- handler := NewEventRsvpHandler(eventRsvpService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.GetEventRsvpsByEventID(w, r)
- }
-}
-
-func GetEventRsvpsByUserIDHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- eventRsvpService := dynamodb_service.NewEventRsvpService()
- handler := NewEventRsvpHandler(eventRsvpService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.GetEventRsvpsByUserID(w, r)
- }
-}
-
-// UpdateEventRsvpHandler is a wrapper that creates the UserHandler and returns the handler function for updating a eventRsvp
-func UpdateEventRsvpHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- eventRsvpService := dynamodb_service.NewEventRsvpService()
- handler := NewEventRsvpHandler(eventRsvpService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.UpdateEventRsvp(w, r)
- }
-}
-
-// DeleteEventRsvpHandler is a wrapper that creates the UserHandler and returns the handler function for deleting a eventRsvp
-func DeleteEventRsvpHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- eventRsvpService := dynamodb_service.NewEventRsvpService()
- handler := NewEventRsvpHandler(eventRsvpService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.DeleteEventRsvp(w, r)
- }
-}
-
diff --git a/functions/gateway/handlers/dynamodb_handlers/event_rsvp_handlers_test.go b/functions/gateway/handlers/dynamodb_handlers/event_rsvp_handlers_test.go
deleted file mode 100644
index f31000c0..00000000
--- a/functions/gateway/handlers/dynamodb_handlers/event_rsvp_handlers_test.go
+++ /dev/null
@@ -1,175 +0,0 @@
-package dynamodb_handlers
-
-import (
- "context"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
-
- "bytes"
- "encoding/json"
-
- "github.com/gorilla/mux"
- dynamodb_service "github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
- internal_types "github.com/meetnearme/api/functions/gateway/types"
-)
-
-// other imports remain unchanged
-
-// Modify your TestInsertEventRsvp function to include a request body
-func TestInsertEventRsvp(t *testing.T) {
- mockService := &dynamodb_service.MockEventRsvpService{
- InsertEventRsvpFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventRsvp internal_types.EventRsvpInsert) (*internal_types.EventRsvp, error) {
- return &internal_types.EventRsvp{EventID: eventRsvp.EventID, UserID: eventRsvp.UserID}, nil
- },
- }
-
- handler := NewEventRsvpHandler(mockService)
-
- // Constructing a JSON body
- body := `{
- "event_id": "event123",
- "user_id": "user123",
- "event_source_type": "someType",
- "event_source_id": "someSourceID",
- "status": "someStatus"
- }`
-
- req := httptest.NewRequest(http.MethodPost, "/rsvp/event123/user123", strings.NewReader(body))
- req.Header.Set("Content-Type", "application/json")
- req = mux.SetURLVars(req, map[string]string{"event_id": "event123", "user_id": "user123"})
-
- w := httptest.NewRecorder()
- handler.CreateEventRsvp(w, req)
-
- res := w.Result()
- if res.StatusCode != http.StatusCreated {
- t.Errorf("Expected status code 200, got %d", res.StatusCode)
- }
-}
-
-// TestUpdateEventRsvp tests updating an RSVP using a mock service.
-func TestUpdateEventRsvp(t *testing.T) {
- mockService := &dynamodb_service.MockEventRsvpService{
- UpdateEventRsvpFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, eventRsvp internal_types.EventRsvpUpdate) (*internal_types.EventRsvp, error) {
- return &internal_types.EventRsvp{EventID: eventId, UserID: userId}, nil
- },
- }
-
- handler := NewEventRsvpHandler(mockService)
-
- // Create a valid JSON payload
- eventRsvp := internal_types.EventRsvpUpdate{
- // Populate this struct as needed for your test
- }
- payload, _ := json.Marshal(eventRsvp) // Handle the error properly in production code
-
- req := httptest.NewRequest(http.MethodPut, "/rsvp/event123/user123", bytes.NewBuffer(payload))
- req.Header.Set("Content-Type", "application/json") // Set the content type header
- req = mux.SetURLVars(req, map[string]string{"event_id": "event123", "user_id": "user123"})
-
- w := httptest.NewRecorder()
- handler.UpdateEventRsvp(w, req)
-
- res := w.Result()
- if res.StatusCode != http.StatusOK {
- t.Errorf("Expected status code 200, got %d", res.StatusCode)
- }
-}
-
-
-// TestGetEventRsvpByPk tests fetching RSVP by primary key using a mock service.
-func TestGetEventRsvpByPk(t *testing.T) {
- mockService := &dynamodb_service.MockEventRsvpService{
- GetEventRsvpByPkFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.EventRsvp, error) {
- return &internal_types.EventRsvp{EventID: eventId, UserID: userId}, nil
- },
- }
-
- handler := NewEventRsvpHandler(mockService)
-
- req := httptest.NewRequest(http.MethodGet, "/rsvp/event123/user123", nil)
- req = mux.SetURLVars(req, map[string]string{"event_id": "event123", "user_id": "user123"})
-
- w := httptest.NewRecorder()
- handler.GetEventRsvpByPk(w, req)
-
- res := w.Result()
- if res.StatusCode != http.StatusOK {
- t.Errorf("Expected status code 200, got %d", res.StatusCode)
- }
-}
-
-// TestGetEventRsvpsByUserID tests fetching RSVPs by user ID using a mock service.
-func TestGetEventRsvpsByUserID(t *testing.T) {
- mockService := &dynamodb_service.MockEventRsvpService{
- GetEventRsvpsByUserIDFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string) ([]internal_types.EventRsvp, error) {
- return []internal_types.EventRsvp{
- {EventID: "event123", UserID: userId},
- {EventID: "event456", UserID: userId},
- }, nil
- },
- }
-
- handler := NewEventRsvpHandler(mockService)
-
- req := httptest.NewRequest(http.MethodGet, "/rsvp/user_id", nil)
- req = mux.SetURLVars(req, map[string]string{"user_id": "user123"})
-
- w := httptest.NewRecorder()
- handler.GetEventRsvpsByUserID(w, req)
-
- res := w.Result()
- if res.StatusCode != http.StatusOK {
- t.Errorf("Expected status code 200, got %d", res.StatusCode)
- }
-}
-
-// TestGetEventRsvpsByEventID tests fetching RSVPs by event ID using a mock service.
-func TestGetEventRsvpsByEventID(t *testing.T) {
- mockService := &dynamodb_service.MockEventRsvpService{
- GetEventRsvpsByEventIDFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string) ([]internal_types.EventRsvp, error) {
- return []internal_types.EventRsvp{
- {EventID: eventId, UserID: "user123"},
- {EventID: eventId, UserID: "user456"},
- }, nil
- },
- }
-
- handler := NewEventRsvpHandler(mockService)
-
- req := httptest.NewRequest(http.MethodGet, "/rsvp/event_id", nil)
- req = mux.SetURLVars(req, map[string]string{"event_id": "event123"})
-
- w := httptest.NewRecorder()
- handler.GetEventRsvpsByEventID(w, req)
-
- res := w.Result()
- if res.StatusCode != http.StatusOK {
- t.Errorf("Expected status code 200, got %d", res.StatusCode)
- }
-}
-
-// TestDeleteEventRsvp tests deleting an RSVP using a mock service.
-func TestDeleteEventRsvp(t *testing.T) {
- mockService := &dynamodb_service.MockEventRsvpService{
- DeleteEventRsvpFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error {
- return nil
- },
- }
-
- handler := NewEventRsvpHandler(mockService)
-
- req := httptest.NewRequest(http.MethodDelete, "/rsvp/event123/user123", nil)
- req = mux.SetURLVars(req, map[string]string{"event_id": "event123", "user_id": "user123"})
-
- w := httptest.NewRecorder()
- handler.DeleteEventRsvp(w, req)
-
- res := w.Result()
- if res.StatusCode != http.StatusOK {
- t.Errorf("Expected status code 200, got %d", res.StatusCode)
- }
-}
-
diff --git a/functions/gateway/handlers/dynamodb_handlers/purchasables_handlers.go b/functions/gateway/handlers/dynamodb_handlers/purchasables_handlers.go
index 53cc112d..6ac733ff 100644
--- a/functions/gateway/handlers/dynamodb_handlers/purchasables_handlers.go
+++ b/functions/gateway/handlers/dynamodb_handlers/purchasables_handlers.go
@@ -7,12 +7,15 @@ import (
"net/http"
"time"
+ "github.com/go-playground/validator"
"github.com/gorilla/mux"
"github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
"github.com/meetnearme/api/functions/gateway/transport"
internal_types "github.com/meetnearme/api/functions/gateway/types"
)
+var validate *validator.Validate = validator.New()
+
type PurchasableHandler struct {
PurchasableService internal_types.PurchasableServiceInterface
}
diff --git a/functions/gateway/handlers/dynamodb_handlers/purchase_handlers.go b/functions/gateway/handlers/dynamodb_handlers/purchase_handlers.go
index a15aa734..0bcbfc7c 100644
--- a/functions/gateway/handlers/dynamodb_handlers/purchase_handlers.go
+++ b/functions/gateway/handlers/dynamodb_handlers/purchase_handlers.go
@@ -11,6 +11,7 @@ import (
dynamodb_types "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/gorilla/mux"
"github.com/meetnearme/api/functions/gateway/helpers"
+ "github.com/meetnearme/api/functions/gateway/services"
dynamodb_service "github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
"github.com/meetnearme/api/functions/gateway/transport"
internal_types "github.com/meetnearme/api/functions/gateway/types"
@@ -185,11 +186,48 @@ func (h *PurchaseHandler) GetPurchasesByUserID(w http.ResponseWriter, r *http.Re
func (h *PurchaseHandler) GetPurchasesByEventID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
- id := vars["event_id"]
- if id == "" {
- transport.SendServerRes(w, []byte("Missing event_id ID"), http.StatusBadRequest, nil)
+ ctx := r.Context()
+ eventId := vars["event_id"]
+ if eventId == "" {
+ transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
+ return
+ }
+
+ // Get user info from context
+ userInfo := helpers.UserInfo{}
+ if _, ok := ctx.Value("userInfo").(helpers.UserInfo); ok {
+ userInfo = ctx.Value("userInfo").(helpers.UserInfo)
+ }
+ userId := userInfo.Sub
+ if userId == "" {
+ transport.SendServerRes(w, []byte("You must be logged in to view this event's purchases"), http.StatusUnauthorized, nil)
+ return
+ }
+
+ roleClaims := []helpers.RoleClaim{}
+ if _, ok := ctx.Value("roleClaims").([]helpers.RoleClaim); ok {
+ roleClaims = ctx.Value("roleClaims").([]helpers.RoleClaim)
+ }
+ // Validate event ownership
+ marqoClient, err := services.GetMarqoClient()
+ if err != nil {
+ transport.SendServerRes(w, []byte("Failed to get Marqo client: "+err.Error()), http.StatusInternalServerError, err)
+ return
+ }
+ event, err := services.GetMarqoEventByID(marqoClient, eventId, "")
+ if err != nil {
+ transport.SendServerRes(w, []byte("Failed to get event: "+err.Error()), http.StatusInternalServerError, err)
return
}
+
+ canEdit := helpers.CanEditEvent(event, &userInfo, roleClaims)
+
+ if !canEdit {
+ transport.SendServerRes(w, []byte("You are not authorized to view this event's purchases"), http.StatusForbidden, nil)
+ return
+ }
+
+ // Handle pagination
limit := r.URL.Query().Get("limit")
limitInt, err := strconv.ParseInt(limit, 10, 32)
if err != nil || limit == "" {
@@ -198,7 +236,7 @@ func (h *PurchaseHandler) GetPurchasesByEventID(w http.ResponseWriter, r *http.R
startKey := r.URL.Query().Get("start_key")
db := transport.GetDB()
- purchases, lastEvaluatedKey, err := h.PurchaseService.GetPurchasesByEventID(r.Context(), db, id, int32(limitInt), startKey)
+ purchases, lastEvaluatedKey, err := h.PurchaseService.GetPurchasesByEventID(r.Context(), db, eventId, int32(limitInt), startKey)
if err != nil {
transport.SendServerRes(w, []byte("Failed to get event's purchases: "+err.Error()), http.StatusInternalServerError, err)
return
diff --git a/functions/gateway/handlers/dynamodb_handlers/registration_handlers_test.go b/functions/gateway/handlers/dynamodb_handlers/purchase_handlers_test.go
similarity index 56%
rename from functions/gateway/handlers/dynamodb_handlers/registration_handlers_test.go
rename to functions/gateway/handlers/dynamodb_handlers/purchase_handlers_test.go
index 9c46992e..e55badf2 100644
--- a/functions/gateway/handlers/dynamodb_handlers/registration_handlers_test.go
+++ b/functions/gateway/handlers/dynamodb_handlers/purchase_handlers_test.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "io"
"net/http"
"net/http/httptest"
"os"
@@ -16,10 +17,11 @@ import (
"github.com/meetnearme/api/functions/gateway/helpers"
dynamodb_service "github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
"github.com/meetnearme/api/functions/gateway/test_helpers"
+ "github.com/meetnearme/api/functions/gateway/types"
internal_types "github.com/meetnearme/api/functions/gateway/types"
)
-func TestGetRegistrationsByEventID(t *testing.T) {
+func TestGetPurchasesByEventID(t *testing.T) {
os.Setenv("GO_ENV", helpers.GO_TEST_ENV)
defer os.Unsetenv("GO_ENV")
// Save original environment variables
@@ -52,7 +54,6 @@ func TestGetRegistrationsByEventID(t *testing.T) {
testEventDescription = "This is a test event"
)
- t.Log("54 << got to")
loc, _ := time.LoadLocation("America/New_York")
testEventStartTime, tmErr := helpers.UtcToUnix64("2099-05-01T12:00:00Z", loc)
if tmErr != nil || testEventStartTime == 0 {
@@ -81,7 +82,6 @@ func TestGetRegistrationsByEventID(t *testing.T) {
w.WriteHeader(http.StatusOK)
w.Write(responseBytes)
}))
- t.Log("84 << got to")
// Set up mock Marqo server
mockMarqoServer.Listener.Close()
listener, err := test_helpers.BindToPort(t, testMarqoEndpoint)
@@ -96,7 +96,6 @@ func TestGetRegistrationsByEventID(t *testing.T) {
boundAddress := mockMarqoServer.Listener.Addr().String()
os.Setenv("DEV_MARQO_API_BASE_URL", fmt.Sprintf("http://%s", boundAddress))
- t.Log("90 << got to")
tests := []struct {
name string
userID string
@@ -113,21 +112,19 @@ func TestGetRegistrationsByEventID(t *testing.T) {
name: "unauthorized user",
userID: "unauthorized_user",
expectedCode: http.StatusForbidden,
- expectedError: "You are not authorized to view this event's registrations",
+ expectedError: "You are not authorized to view this event's purchases",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- t.Log("113 << got to")
- mockService := &dynamodb_service.MockRegistrationService{
- GetRegistrationsByEventIDFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string, limit int32, startKey string) ([]internal_types.Registration, map[string]dynamodb_types.AttributeValue, error) {
- return []internal_types.Registration{{EventId: eventId, UserId: "user1"}}, nil, nil
+ mockService := &dynamodb_service.MockPurchaseService{
+ GetPurchasesByEventIDFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string, limit int32, startKey string) ([]internal_types.Purchase, map[string]dynamodb_types.AttributeValue, error) {
+ return []internal_types.Purchase{{EventID: eventId, UserID: "user1"}}, nil, nil
},
}
- handler := NewRegistrationHandler(mockService)
- t.Log("120 << got to")
- req := httptest.NewRequest(http.MethodGet, "/registrations/event_id", nil)
+ handler := NewPurchaseHandler(mockService)
+ req := httptest.NewRequest(http.MethodGet, "/purchases/event_id", nil)
req = mux.SetURLVars(req, map[string]string{"event_id": testEventID})
// Add authentication context with test user
@@ -138,62 +135,112 @@ func TestGetRegistrationsByEventID(t *testing.T) {
req = req.WithContext(ctx)
w := httptest.NewRecorder()
- handler.GetRegistrationsByEventID(w, req)
- t.Log("133 << got to")
+ handler.GetPurchasesByEventID(w, req)
res := w.Result()
if res.StatusCode != tt.expectedCode {
t.Errorf("Expected status code %d, got %d", tt.expectedCode, res.StatusCode)
}
+ responseBody, _ := io.ReadAll(res.Body)
// If we expect an error, verify the error message
if tt.expectedError != "" {
- var response map[string]interface{}
- if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
- t.Fatalf("Failed to decode response: %v", err)
+ if !strings.Contains(string(responseBody), tt.expectedError) {
+ t.Errorf("Expected error message to contain %q, got %q", tt.expectedError, string(responseBody))
}
+ }
+ })
+ }
+}
- if msg, ok := response["error"].(map[string]interface{})["message"].(string); !ok ||
- !strings.Contains(msg, tt.expectedError) {
- t.Errorf("Expected error message to contain %q, got %q", tt.expectedError, msg)
+func TestGetPurchasesByUserID(t *testing.T) {
+ tests := []struct {
+ name string
+ requestUserID string // user ID in the request path
+ contextUserID string // user ID in the context
+ expectedCode int
+ expectedError string
+ }{
+ {
+ name: "authorized user",
+ requestUserID: "test_user_123",
+ contextUserID: "test_user_123",
+ expectedCode: http.StatusOK,
+ },
+ {
+ name: "unauthorized user",
+ requestUserID: "test_user_123",
+ contextUserID: "different_user",
+ expectedCode: http.StatusForbidden,
+ expectedError: "You are not authorized to view this user's purchases",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ mockService := &dynamodb_service.MockPurchaseService{
+ GetPurchasesByUserIDFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string, limit int32, startKey string) ([]internal_types.Purchase, map[string]dynamodb_types.AttributeValue, error) {
+ return []internal_types.Purchase{{UserID: userId}}, nil, nil
+ },
+ }
+ handler := NewPurchaseHandler(mockService)
+ req := httptest.NewRequest(http.MethodGet, "/purchases/user_id", nil)
+ req = mux.SetURLVars(req, map[string]string{"user_id": tt.requestUserID})
+
+ // Add authentication context with test user
+ userInfo := helpers.UserInfo{
+ Sub: tt.contextUserID,
+ }
+ ctx := context.WithValue(req.Context(), "userInfo", userInfo)
+ req = req.WithContext(ctx)
+
+ w := httptest.NewRecorder()
+ handler.GetPurchasesByUserID(w, req)
+ res := w.Result()
+ if res.StatusCode != tt.expectedCode {
+ t.Errorf("Expected status code %d, got %d", tt.expectedCode, res.StatusCode)
+ }
+ responseBody, _ := io.ReadAll(res.Body)
+ // If we expect an error, verify the error message
+ if tt.expectedError != "" {
+ if !strings.Contains(string(responseBody), tt.expectedError) {
+ t.Errorf("Expected error message to contain %q, got %q", tt.expectedError, string(responseBody))
}
}
})
}
}
-func TestGetRegistrationsByUserID(t *testing.T) {
- mockService := &dynamodb_service.MockRegistrationService{
- GetRegistrationsByUserIDFunc: func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string) ([]internal_types.Registration, error) {
- return []internal_types.Registration{{EventId: "event1", UserId: userId}}, nil
+func TestDeletePurchase(t *testing.T) {
+ mockService := &dynamodb_service.MockPurchaseService{
+ DeletePurchaseFunc: func(ctx context.Context, dynamodbClient types.DynamoDBAPI, eventId string, userId string) error {
+ return nil
},
}
- handler := NewRegistrationHandler(mockService)
+ handler := NewPurchaseHandler(mockService)
+
+ // Add user context
+ const (
+ testEventID = "event-123"
+ testUserID = "user-456"
+ )
- // Create request with user_id in path params
- req := httptest.NewRequest(http.MethodGet, "/registrations/user123", nil)
- req = mux.SetURLVars(req, map[string]string{"user_id": "user123"})
+ req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/purchases/%s/%s", testEventID, testUserID), nil)
+ req = mux.SetURLVars(req, map[string]string{
+ "event_id": testEventID,
+ "user_id": testUserID,
+ })
- // Create context with user info
+ // Add user context
userInfo := helpers.UserInfo{
- Sub: "user123", // This should match the user_id in path params
- // Add other required UserInfo fields if needed
+ Sub: testUserID, // Using the same user ID to test authorized deletion
}
ctx := context.WithValue(req.Context(), "userInfo", userInfo)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
- handler.GetRegistrationsByUserID(w, req)
+ handler.DeletePurchase(w, req)
res := w.Result()
if res.StatusCode != http.StatusOK {
t.Errorf("Expected status code 200, got %d", res.StatusCode)
}
-
- // Optionally verify response body
- var response []internal_types.Registration
- if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
- t.Fatalf("Failed to decode response: %v", err)
- }
- if len(response) != 1 || response[0].UserId != "user123" {
- t.Errorf("Unexpected response content")
- }
}
diff --git a/functions/gateway/handlers/dynamodb_handlers/registration_handlers.go b/functions/gateway/handlers/dynamodb_handlers/registration_handlers.go
deleted file mode 100644
index 772e9392..00000000
--- a/functions/gateway/handlers/dynamodb_handlers/registration_handlers.go
+++ /dev/null
@@ -1,370 +0,0 @@
-package dynamodb_handlers
-
-import (
- "encoding/json"
- "io"
- "log"
- "net/http"
- "strconv"
- "time"
-
- dynamodb_types "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
- "github.com/go-playground/validator"
- "github.com/gorilla/mux"
- "github.com/meetnearme/api/functions/gateway/helpers"
- "github.com/meetnearme/api/functions/gateway/services"
- dynamodb_service "github.com/meetnearme/api/functions/gateway/services/dynamodb_service"
- "github.com/meetnearme/api/functions/gateway/transport"
- internal_types "github.com/meetnearme/api/functions/gateway/types"
-)
-
-// Validator instance for struct validation
-var validate *validator.Validate = validator.New()
-
-func init() {
- db = transport.CreateDbClient()
-}
-
-// UserHandler handles user-related requests
-type RegistrationHandler struct {
- RegistrationService internal_types.RegistrationServiceInterface
-}
-
-// NewRegistrationHandler creates a new RegistrationHandler with the given RegistrationService
-func NewRegistrationHandler(registrationService internal_types.RegistrationServiceInterface) *RegistrationHandler {
- return &RegistrationHandler{RegistrationService: registrationService}
-}
-
-func (h *RegistrationHandler) CreateRegistration(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- var createRegistration internal_types.RegistrationInsert
- body, err := io.ReadAll(r.Body)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to read request body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- err = json.Unmarshal(body, &createRegistration)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid JSON payload: "+err.Error()), http.StatusUnprocessableEntity, err)
- return
- }
-
- createRegistration.CreatedAt = time.Now()
- createRegistration.UpdatedAt = time.Now()
- createRegistration.EventId = eventId
- createRegistration.UserId = userId
-
- err = validate.Struct(&createRegistration)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- res, err := h.RegistrationService.InsertRegistration(r.Context(), db, createRegistration, eventId, userId)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to create registration fields: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- response, err := json.Marshal(res)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusCreated, nil)
-}
-
-func (h *RegistrationHandler) GetRegistrationByPk(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
-
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- registration, err := h.RegistrationService.GetRegistrationByPk(r.Context(), db, eventId, userId)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get registrations: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- response, err := json.Marshal(registration)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-// This needs to change for use cases of fetching multiple users based on org ID or other
-func (h *RegistrationHandler) GetRegistrationsByEventID(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- ctx := r.Context()
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
-
- // Get user info from context
- userInfo := helpers.UserInfo{}
- if _, ok := ctx.Value("userInfo").(helpers.UserInfo); ok {
- userInfo = ctx.Value("userInfo").(helpers.UserInfo)
- }
- userId := userInfo.Sub
- if userId == "" {
- transport.SendServerRes(w, []byte("You must be logged in to view this event's registrations"), http.StatusUnauthorized, nil)
- return
- }
-
- roleClaims := []helpers.RoleClaim{}
- if _, ok := ctx.Value("roleClaims").([]helpers.RoleClaim); ok {
- roleClaims = ctx.Value("roleClaims").([]helpers.RoleClaim)
- }
- // Validate event ownership
- marqoClient, err := services.GetMarqoClient()
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get Marqo client: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
- event, err := services.GetMarqoEventByID(marqoClient, eventId, "")
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get event: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- canEdit := helpers.CanEditEvent(event, &userInfo, roleClaims)
-
- if !canEdit {
- transport.SendServerRes(w, []byte("You are not authorized to view this event's registrations"), http.StatusForbidden, nil)
- return
- }
-
- // Handle pagination
- limit := r.URL.Query().Get("limit")
- limitInt, err := strconv.ParseInt(limit, 10, 32)
- if err != nil || limit == "" {
- limitInt = helpers.DEFAULT_PAGINATION_LIMIT
- }
- startKey := r.URL.Query().Get("start_key")
-
- // Get registrations
- db := transport.GetDB()
- registrations, lastEvaluatedKey, err := h.RegistrationService.GetRegistrationsByEventID(ctx, db, eventId, int32(limitInt), startKey)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get registrations: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
- responseData := struct {
- Count int `json:"count"`
- NextKey map[string]dynamodb_types.AttributeValue `json:"nextKey"`
- Registrations []internal_types.Registration `json:"registrations"`
- }{
- Count: len(registrations),
- NextKey: lastEvaluatedKey,
- Registrations: registrations,
- }
-
- response, err := json.Marshal(responseData)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *RegistrationHandler) GetRegistrationsByUserID(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- ctx := r.Context()
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
-
- // Get user info from context
- userInfo := helpers.UserInfo{}
- if _, ok := ctx.Value("userInfo").(helpers.UserInfo); ok {
- userInfo = ctx.Value("userInfo").(helpers.UserInfo)
- }
- _userId := userInfo.Sub
-
- if _userId == "" {
- transport.SendServerRes(w, []byte("You must be loggged in to get your registrations"), http.StatusBadRequest, nil)
- return
- }
-
- if _userId != userId {
- transport.SendServerRes(w, []byte("You are not authorized to view this user's registrations"), http.StatusForbidden, nil)
- return
- }
-
- registration, err := h.RegistrationService.GetRegistrationsByUserID(r.Context(), db, userId)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to get registrations: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- response, err := json.Marshal(registration)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *RegistrationHandler) UpdateRegistration(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing event ID"), http.StatusBadRequest, nil)
- return
- }
-
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- var updateRegistration internal_types.RegistrationUpdate
- body, err := io.ReadAll(r.Body)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to read request body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- updateRegistration.UpdatedAt = time.Now()
-
- err = json.Unmarshal(body, &updateRegistration)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid JSON payload: "+err.Error()), http.StatusUnprocessableEntity, err)
- return
- }
-
- updateRegistration.UserId = userId
- updateRegistration.EventId = eventId
-
- err = validate.Struct(&updateRegistration)
- if err != nil {
- transport.SendServerRes(w, []byte("Invalid body: "+err.Error()), http.StatusBadRequest, err)
- return
- }
-
- updatedRegistration, err := h.RegistrationService.UpdateRegistration(r.Context(), db, eventId, userId, updateRegistration)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to update user: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- if updatedRegistration == nil {
- transport.SendServerRes(w, []byte("Registration not found"), http.StatusNotFound, nil)
- return
- }
-
- response, err := json.Marshal(updatedRegistration)
- if err != nil {
- transport.SendServerRes(w, []byte("Error marshaling JSON"), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, response, http.StatusOK, nil)
-}
-
-func (h *RegistrationHandler) DeleteRegistration(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- eventId := vars["event_id"]
- if eventId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- userId := vars["user_id"]
- if userId == "" {
- transport.SendServerRes(w, []byte("Missing user ID"), http.StatusBadRequest, nil)
- return
- }
-
- err := h.RegistrationService.DeleteRegistration(r.Context(), db, eventId, userId)
- if err != nil {
- transport.SendServerRes(w, []byte("Failed to delete user: "+err.Error()), http.StatusInternalServerError, err)
- return
- }
-
- transport.SendServerRes(w, []byte("Registration successfully deleted"), http.StatusOK, nil)
-}
-
-func CreateRegistrationHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- log.Printf("in reg fields wrapper")
- registrationService := dynamodb_service.NewRegistrationService()
- handler := NewRegistrationHandler(registrationService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.CreateRegistration(w, r)
- }
-}
-
-// GetRegistrationsHandler is a wrapper that creates the RegistrationHandler and returns the handler function for getting all users
-func GetRegistrationByPkHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- registrationService := dynamodb_service.NewRegistrationService()
- handler := NewRegistrationHandler(registrationService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.GetRegistrationByPk(w, r)
- }
-}
-
-// GetRegistrationsHandler is a wrapper that creates the RegistrationHandler and returns the handler function for getting all users
-func GetRegistrationsByEventIDHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- registrationService := dynamodb_service.NewRegistrationService()
- handler := NewRegistrationHandler(registrationService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.GetRegistrationsByEventID(w, r)
- }
-}
-
-func GetRegistrationsByUserIDHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- registrationService := dynamodb_service.NewRegistrationService()
- handler := NewRegistrationHandler(registrationService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.GetRegistrationsByUserID(w, r)
- }
-}
-
-// UpdateRegistrationHandler is a wrapper that creates the RegistrationHandler and returns the handler function for updating a user
-func UpdateRegistrationHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- registrationService := dynamodb_service.NewRegistrationService()
- handler := NewRegistrationHandler(registrationService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.UpdateRegistration(w, r)
- }
-}
-
-// DeleteRegistrationHandler is a wrapper that creates the RegistrationHandler and returns the handler function for deleting a user
-func DeleteRegistrationHandler(w http.ResponseWriter, r *http.Request) http.HandlerFunc {
- registrationService := dynamodb_service.NewRegistrationService()
- handler := NewRegistrationHandler(registrationService)
- return func(w http.ResponseWriter, r *http.Request) {
- handler.DeleteRegistration(w, r)
- }
-}
diff --git a/functions/gateway/handlers/page_handlers.go b/functions/gateway/handlers/page_handlers.go
index 22878e06..c00cfb37 100644
--- a/functions/gateway/handlers/page_handlers.go
+++ b/functions/gateway/handlers/page_handlers.go
@@ -463,7 +463,7 @@ func GetEventDetailsPage(w http.ResponseWriter, r *http.Request) http.HandlerFun
canEdit := helpers.CanEditEvent(event, &userInfo, roleClaims)
checkoutParamVal := r.URL.Query().Get("checkout")
- eventDetailsPage := pages.EventDetailsPage(*event, checkoutParamVal, canEdit)
+ eventDetailsPage := pages.EventDetailsPage(*event, userInfo, checkoutParamVal, canEdit)
layoutTemplate := pages.Layout(helpers.SitePages["event-detail"], userInfo, eventDetailsPage, *event)
var buf bytes.Buffer
err = layoutTemplate.Render(ctx, &buf)
diff --git a/functions/gateway/helpers/constants.go b/functions/gateway/helpers/constants.go
index f8b8e868..c294ff67 100644
--- a/functions/gateway/helpers/constants.go
+++ b/functions/gateway/helpers/constants.go
@@ -76,16 +76,20 @@ type UserInfo struct {
Metadata string `json:"metadata"`
}
-type StripeCheckoutStatuses struct {
- Settled string
- Pending string
- Canceled string
+type PurchaseStatuses struct {
+ Settled string
+ Pending string
+ Canceled string
+ Registered string
+ Interested string
}
-var StripeCheckoutStatus = StripeCheckoutStatuses{
- Settled: "SETTLED",
- Pending: "PENDING",
- Canceled: "CANCELED",
+var PurchaseStatus = PurchaseStatuses{
+ Settled: "SETTLED",
+ Pending: "PENDING",
+ Canceled: "CANCELED",
+ Registered: "REGISTERED",
+ Interested: "INTERESTED",
}
// RoleClaim represents a formatted role claim.
@@ -134,16 +138,18 @@ type SitePage struct {
}
var SitePages = map[string]SitePage{
+ // NOTE: the {trailingslash:\\/?} is required for a route to match with or without a trailing slash, the
+ // solution is from this github comment (see discussion as well) https://github.com/gorilla/mux/issues/30#issuecomment-1666428538
"home": {Key: "home", Slug: "/", Name: "Home", SubnavItems: []string{SubnavItems[NvMain], SubnavItems[NvFilters]}},
- "about": {Key: "about", Slug: "/about", Name: "About", SubnavItems: []string{SubnavItems[NvMain]}},
- "profile": {Key: "profile", Slug: "/admin/profile", Name: "Profile", SubnavItems: []string{SubnavItems[NvMain]}},
- "add-event-source": {Key: "add-event-source", Slug: "/admin/add-event-source", Name: "Add Event Source", SubnavItems: []string{SubnavItems[NvMain]}},
- "settings": {Key: "settings", Slug: "/admin/profile/settings", Name: "Settings", SubnavItems: []string{SubnavItems[NvMain]}},
- "map-embed": {Key: "map-embed", Slug: "/map-embed", Name: "MapEmbed", SubnavItems: []string{SubnavItems[NvMain]}},
- "event-detail": {Key: "event-detail", Slug: "/event/{" + EVENT_ID_KEY + "}", Name: "Event Detail", SubnavItems: []string{SubnavItems[NvMain], SubnavItems[NvCart]}},
- "add-event": {Key: "add-event", Slug: "/admin/event/new", Name: "Add Event", SubnavItems: []string{SubnavItems[NvMain]}},
- "edit-event": {Key: "edit-event", Slug: "/admin/event/{" + EVENT_ID_KEY + "}/edit", Name: "Edit Event", SubnavItems: []string{SubnavItems[NvMain]}},
- "attendees-event": {Key: "attendees-event", Slug: "/admin/event/{" + EVENT_ID_KEY + "}/attendees", Name: "Event Attendees", SubnavItems: []string{SubnavItems[NvMain]}},
+ "about": {Key: "about", Slug: "/about{trailingslash:\\/?}", Name: "About", SubnavItems: []string{SubnavItems[NvMain]}},
+ "profile": {Key: "profile", Slug: "/admin/profile{trailingslash:\\/?}", Name: "Profile", SubnavItems: []string{SubnavItems[NvMain]}},
+ "add-event-source": {Key: "add-event-source", Slug: "/admin/add-event-source{trailingslash:\\/?}", Name: "Add Event Source", SubnavItems: []string{SubnavItems[NvMain]}},
+ "settings": {Key: "settings", Slug: "/admin/profile/settings{trailingslash:\\/?}", Name: "Settings", SubnavItems: []string{SubnavItems[NvMain]}},
+ "map-embed": {Key: "map-embed", Slug: "/map-embed{trailingslash:\\/?}", Name: "MapEmbed", SubnavItems: []string{SubnavItems[NvMain]}},
+ "event-detail": {Key: "event-detail", Slug: "/event/{" + EVENT_ID_KEY + "}{trailingslash:\\/?}", Name: "Event Detail", SubnavItems: []string{SubnavItems[NvMain], SubnavItems[NvCart]}},
+ "add-event": {Key: "add-event", Slug: "/admin/event/new{trailingslash:\\/?}", Name: "Add Event", SubnavItems: []string{SubnavItems[NvMain]}},
+ "edit-event": {Key: "edit-event", Slug: "/admin/event/{" + EVENT_ID_KEY + "}/edit{trailingslash:\\/?}", Name: "Edit Event", SubnavItems: []string{SubnavItems[NvMain]}},
+ "attendees-event": {Key: "attendees-event", Slug: "/admin/event/{" + EVENT_ID_KEY + "}/attendees{trailingslash:\\/?}", Name: "Event Attendees", SubnavItems: []string{SubnavItems[NvMain]}},
}
type Subcategory struct {
diff --git a/functions/gateway/helpers/utils_test.go b/functions/gateway/helpers/utils_test.go
index d4525e2f..066cf615 100644
--- a/functions/gateway/helpers/utils_test.go
+++ b/functions/gateway/helpers/utils_test.go
@@ -187,8 +187,8 @@ func TestSetCloudFlareKV(t *testing.T) {
mockCloudflareServer.Start()
defer mockCloudflareServer.Close()
- boundCfAddress := mockCloudflareServer.Listener.Addr().String()
- os.Setenv("ZITADEL_INSTANCE_HOST", boundCfAddress)
+ boundCfAddress := fmt.Sprintf("http://%s", mockCloudflareServer.Listener.Addr().String())
+ os.Setenv("CLOUDFLARE_API_BASE_URL", boundCfAddress)
zitadelListener, err := test_helpers.BindToPort(t, zitadelEndpoint)
if err != nil {
diff --git a/functions/gateway/main.go b/functions/gateway/main.go
index 02970343..da2697e7 100644
--- a/functions/gateway/main.go
+++ b/functions/gateway/main.go
@@ -62,7 +62,7 @@ func init() {
// from `Require` which always creates a new session if the user isn't logged in...
// the complexity is we might want "in the middle", which would be "auto-refresh
// the session, but DO NOT redirect to /login if the user's session is expired'"
- // session duration might be a Zitadel configuration issue
+ // session duration might be a ZFvitadel configuration issue
{helpers.SitePages["event-detail"].Slug, "GET", handlers.GetEventDetailsPage, Check},
// API routes
@@ -94,35 +94,19 @@ func init() {
{"/api/purchasables/{event_id:[0-9a-fA-F-]+}", "PUT", dynamodb_handlers.UpdatePurchasableHandler, Require}, // Update an existing purchasable
{"/api/purchasables/{event_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeletePurchasableHandler, Require}, // Delete a purchasable
- // // Event RSVPs routes
- {"/api/event-rsvps/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "POST", dynamodb_handlers.CreateEventRsvpHandler, None}, // Create a new event RSVP
- {"/api/event-rsvps/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetEventRsvpByPkHandler, Require}, // Get a specific event RSVP
- {"/api/event-rsvps/event/{event_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetEventRsvpsByEventIDHandler, Require}, // Get all event RSVPs
- {"/api/event-rsvps/user/{user_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetEventRsvpsByUserIDHandler, Require}, // Get a specific event RSVP
- {"/api/event-rsvps/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "PUT", dynamodb_handlers.UpdateEventRsvpHandler, Require}, // Update an existing event RSVP
- {"/api/event-rsvps/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeleteEventRsvpHandler, Require}, // Delete an event RSVP
-
- // Registrations
- {"/api/registrations/{event_id:[0-9a-fA-F-]+}/{user_id:(?:anonymous|[0-9a-fA-F-]+)}", "POST", dynamodb_handlers.CreateRegistrationHandler, None}, // Create a new Registration
- {"/api/registrations/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetRegistrationByPkHandler, Require}, // Get a registration by primary key
- {"/api/registrations/user/{user_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetRegistrationsByUserIDHandler, Require}, // Get a specific event RSVP
- {"/api/registrations/event/{event_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetRegistrationsByEventIDHandler, Require}, // Get all event RSVPs
- {"/api/registrations/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "PUT", dynamodb_handlers.UpdateRegistrationHandler, Require}, // Update an existing Registration
- {"/api/registrations/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeleteRegistrationHandler, Require}, // Delete an event RSVP
-
// RegistrationFields
- {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "POST", dynamodb_handlers.CreateRegistrationFieldsHandler, Require}, // Create a new
- {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetRegistrationFieldsByEventIDHandler, None}, // Get all
- {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "PUT", dynamodb_handlers.UpdateRegistrationFieldsHandler, Require}, // Update an existing
- {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeleteRegistrationFieldsHandler, Require}, // Delete an
+ {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "POST", dynamodb_handlers.CreateRegistrationFieldsHandler, Require},
+ {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetRegistrationFieldsByEventIDHandler, None},
+ {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "PUT", dynamodb_handlers.UpdateRegistrationFieldsHandler, Require},
+ {"/api/registration-fields/{event_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeleteRegistrationFieldsHandler, Require},
// Purchases
- {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "POST", dynamodb_handlers.CreatePurchaseHandler, Require}, // Create a new event RSVP
- {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}/{created_at:[0-9]+}", "GET", dynamodb_handlers.GetPurchaseByPkHandler, Require}, // Get a specific event RSVP
- {"/api/purchases/event/{event_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetPurchasesByEventIDHandler, Require}, // Get all event RSVPs
- {"/api/purchases/user/{user_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetPurchasesByUserIDHandler, Require}, // Get a specific event RSVP
- {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}/{created_at:[0-9]+}", "PUT", dynamodb_handlers.UpdatePurchaseHandler, None}, // Update an existing event RSVP
- {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeletePurchaseHandler, None}, // Delete an event RSVP
+ {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "POST", dynamodb_handlers.CreatePurchaseHandler, Require}, // Create a new event Purchase
+ {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}/{created_at:[0-9]+}", "GET", dynamodb_handlers.GetPurchaseByPkHandler, Require}, // Get a specific event Purchase
+ {"/api/purchases/event/{event_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetPurchasesByEventIDHandler, Require}, // Get all event Purchases
+ {"/api/purchases/user/{user_id:[0-9a-fA-F-]+}", "GET", dynamodb_handlers.GetPurchasesByUserIDHandler, Require}, // Get a specific event Purchase
+ {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}/{created_at:[0-9]+}", "PUT", dynamodb_handlers.UpdatePurchaseHandler, None}, // Update an existing event Purchase
+ {"/api/purchases/{event_id:[0-9a-fA-F-]+}/{user_id:[0-9a-fA-F-]+}", "DELETE", dynamodb_handlers.DeletePurchaseHandler, None}, // Delete an event Purchase
// Checkout Session
{"/api/checkout/{event_id:[0-9a-fA-F-]+}", "POST", handlers.CreateCheckoutSessionHandler, Check},
diff --git a/functions/gateway/services/dynamodb_service/event_rsvp_service.go b/functions/gateway/services/dynamodb_service/event_rsvp_service.go
deleted file mode 100644
index bfc5e3d2..00000000
--- a/functions/gateway/services/dynamodb_service/event_rsvp_service.go
+++ /dev/null
@@ -1,264 +0,0 @@
-// TODO: change all fmt to log printout in new rds handlers and services
-package dynamodb_service
-
-import (
- "context"
- "fmt"
- "log"
- "strconv"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
- "github.com/aws/aws-sdk-go-v2/service/dynamodb"
- dynamodb_types "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
- "github.com/meetnearme/api/functions/gateway/helpers"
- internal_types "github.com/meetnearme/api/functions/gateway/types"
-)
-
-var rsvpTableName = helpers.GetDbTableName(helpers.RsvpsTablePrefix)
-
-func init () {
- rsvpTableName = helpers.GetDbTableName(helpers.RsvpsTablePrefix)
-}
-
-type EventRsvpService struct{}
-
-func NewEventRsvpService() internal_types.EventRsvpServiceInterface {
- return &EventRsvpService{}
-}
-
-func (s *EventRsvpService) InsertEventRsvp(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventRsvp internal_types.EventRsvpInsert) (*internal_types.EventRsvp, error) {
- // Validate the eventRsvp object
- if err := validate.Struct(eventRsvp); err != nil {
- return nil, fmt.Errorf("validation failed: %w", err)
- }
-
- item, err := attributevalue.MarshalMap(&eventRsvp)
- if err != nil {
- return nil, err
- }
-
- if (rsvpTableName == "") {
- return nil, fmt.Errorf("ERR: rsvpTableName is empty")
- }
-
- input := &dynamodb.PutItemInput{
- Item: item,
- TableName: aws.String(registrationTableName),
- ConditionExpression: aws.String("attribute_not_exists(eventId) AND attribute_not_exists(userId)"),
- }
-
-
- res, err := dynamodbClient.PutItem(ctx, input)
- if err != nil {
- log.Print("htting error in put item dynamo")
- return nil, err
- }
-
- var insertedRegistration internal_types.EventRsvp
-
- err = attributevalue.UnmarshalMap(res.Attributes, &insertedRegistration)
- if err != nil {
- return nil, err
- }
-
- // return registration, nil
- return &insertedRegistration, nil
-}
-
-
-func (s *EventRsvpService) GetEventRsvpByPk(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.EventRsvp, error) {
- input := &dynamodb.GetItemInput{
- TableName: aws.String(registrationTableName),
- Key: map[string]dynamodb_types.AttributeValue{
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- }
-
- result, err := dynamodbClient.GetItem(ctx, input)
- if err != nil {
- return nil, err
- }
-
- var eventRsvp internal_types.EventRsvp
- err = attributevalue.UnmarshalMap(result.Item, &eventRsvp)
- if err != nil {
- return nil, err
- }
-
- return &eventRsvp, nil
-}
-
-func (s *EventRsvpService) GetEventRsvpsByEventID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string) ([]internal_types.EventRsvp, error) {
- queryInput := &dynamodb.QueryInput{
- TableName: aws.String(registrationTableName),
- KeyConditions: map[string]dynamodb_types.Condition{
- "eventId": {
- ComparisonOperator: dynamodb_types.ComparisonOperatorEq,
- AttributeValueList: []dynamodb_types.AttributeValue{
- &dynamodb_types.AttributeValueMemberS{Value: eventId},
- },
- },
- },
- }
-
- // Run the query with the constructed QueryInput
- result, err := dynamodbClient.Query(ctx, queryInput)
- if err != nil {
- return nil, err
- }
-
- var eventRsvps []internal_types.EventRsvp
- err = attributevalue.UnmarshalListOfMaps(result.Items, &eventRsvps)
- if err != nil {
- return nil, err
- }
-
- return eventRsvps, nil
-}
-
-func (s *EventRsvpService) GetEventRsvpsByUserID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string) ([]internal_types.EventRsvp, error) {
- input := &dynamodb.QueryInput{
- TableName: aws.String(registrationTableName),
- IndexName: aws.String("userIdGsi"), // GSI name
- KeyConditionExpression: aws.String("userId = :userId"),
- ExpressionAttributeValues: map[string]dynamodb_types.AttributeValue{
- ":userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- }
-
- result, err := dynamodbClient.Query(context.TODO(), input)
- if err != nil {
- log.Fatalf("Query GSI failed, %v", err)
- }
- log.Printf("query gsi: %v", result)
-
- inputScan := &dynamodb.ScanInput{
- TableName: aws.String(registrationTableName),
- IndexName: aws.String("userIdGsi"), // Scan the GSI
- }
-
- resultScan, err := dynamodbClient.Scan(ctx, inputScan)
- if err != nil {
- log.Fatalf("Scan GSI failed: %v", err)
- }
-
- log.Printf("GSI scan result: %v", resultScan.Items)
-
- var eventRsvps []internal_types.EventRsvp
- err = attributevalue.UnmarshalListOfMaps(result.Items, &eventRsvps)
- if err != nil {
- return nil, err
- }
-
- return eventRsvps, nil
-}
-
-func (s *EventRsvpService) UpdateEventRsvp(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, eventRsvp internal_types.EventRsvpUpdate) (*internal_types.EventRsvp, error) {
- if rsvpTableName == "" {
- return nil, fmt.Errorf("ERR: rsvpTableName is empty")
- }
- input := &dynamodb.UpdateItemInput{
- TableName: aws.String(rsvpTableName),
- Key: map[string]dynamodb_types.AttributeValue{
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- ExpressionAttributeNames: make(map[string]string),
- ExpressionAttributeValues: make(map[string]dynamodb_types.AttributeValue),
- UpdateExpression: aws.String("SET"),
- ReturnValues: dynamodb_types.ReturnValueAllNew,
- }
-
- if eventRsvp.EventSourceID != "" {
- input.ExpressionAttributeNames["#eventSourceId"] = "eventSourceId"
- input.ExpressionAttributeValues[":eventSourceId"] = &dynamodb_types.AttributeValueMemberS{Value: eventRsvp.EventSourceID}
- *input.UpdateExpression += " #eventSourceId = :eventSourceId,"
- }
-
- if eventRsvp.EventSourceType != "" {
- input.ExpressionAttributeNames["#eventSourceType"] = "eventSourceType"
- input.ExpressionAttributeValues[":eventSourceType"] = &dynamodb_types.AttributeValueMemberS{Value: eventRsvp.EventSourceType}
- *input.UpdateExpression += " #eventSourceType = :eventSourceType,"
- }
-
- if eventRsvp.Status != "" {
- input.ExpressionAttributeNames["#status"] = "status"
- input.ExpressionAttributeValues[":status"] = &dynamodb_types.AttributeValueMemberS{Value: eventRsvp.Status}
- *input.UpdateExpression += " #status = :status,"
- }
-
- // Set the updatedAt field
- currentTime := time.Now().Unix()
- input.ExpressionAttributeNames["#updatedAt"] = "updatedAt"
- input.ExpressionAttributeValues[":updatedAt"] = &dynamodb_types.AttributeValueMemberN{Value: strconv.FormatInt(currentTime, 10)}
- *input.UpdateExpression += "#updatedAt = :updatedAt"
-
- // Execute the update
- res, err := dynamodbClient.UpdateItem(ctx, input)
- if err != nil {
- return nil, err
- }
-
- // Unmarshal the updated registration
- var updatedEventRsvp internal_types.EventRsvp
- err = attributevalue.UnmarshalMap(res.Attributes, &updatedEventRsvp)
- if err != nil {
- return nil, err
- }
-
- return &updatedEventRsvp, nil
-}
-
-func (s *EventRsvpService) DeleteEventRsvp(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error {
- input := &dynamodb.DeleteItemInput{
- TableName: aws.String(registrationTableName),
- Key: map[string]dynamodb_types.AttributeValue{
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- }
-
- _, err := dynamodbClient.DeleteItem(ctx, input)
- if err != nil {
- return err
- }
-
- log.Printf("registration fields successfully deleted")
- return nil
-}
-
-type MockEventRsvpService struct {
- InsertEventRsvpFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventRsvp internal_types.EventRsvpInsert) (*internal_types.EventRsvp, error)
- GetEventRsvpByPkFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.EventRsvp, error)
- GetEventRsvpsByUserIDFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userID string) ([]internal_types.EventRsvp, error) // New function
- GetEventRsvpsByEventIDFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventID string) ([]internal_types.EventRsvp, error) // New function
- UpdateEventRsvpFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, eventRsvp internal_types.EventRsvpUpdate) (*internal_types.EventRsvp, error)
- DeleteEventRsvpFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error
-}
-
-func (m *MockEventRsvpService) InsertEventRsvp(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventRsvp internal_types.EventRsvpInsert) (*internal_types.EventRsvp, error) {
- return m.InsertEventRsvpFunc(ctx, dynamodbClient, eventRsvp)
-}
-
-func (m *MockEventRsvpService) GetEventRsvpByPk(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.EventRsvp, error) {
- return m.GetEventRsvpByPkFunc(ctx, dynamodbClient, eventId, userId)
-}
-
-func (m *MockEventRsvpService) UpdateEventRsvp(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, eventRsvp internal_types.EventRsvpUpdate) (*internal_types.EventRsvp, error) {
- return m.UpdateEventRsvpFunc(ctx, dynamodbClient, eventId, userId, eventRsvp)
-}
-
-func (m *MockEventRsvpService) DeleteEventRsvp(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error {
- return m.DeleteEventRsvpFunc(ctx, dynamodbClient, eventId, userId)
-}
-
-func (m *MockEventRsvpService) GetEventRsvpsByUserID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userID string) ([]internal_types.EventRsvp, error) {
- return m.GetEventRsvpsByUserIDFunc(ctx, dynamodbClient, userID)
-}
-
-func (m *MockEventRsvpService) GetEventRsvpsByEventID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventID string) ([]internal_types.EventRsvp, error) {
- return m.GetEventRsvpsByEventIDFunc(ctx, dynamodbClient, eventID)
-}
diff --git a/functions/gateway/services/dynamodb_service/purchases_service.go b/functions/gateway/services/dynamodb_service/purchases_service.go
index f085da1e..b1d0507b 100644
--- a/functions/gateway/services/dynamodb_service/purchases_service.go
+++ b/functions/gateway/services/dynamodb_service/purchases_service.go
@@ -54,15 +54,14 @@ func (s *PurchaseService) InsertPurchase(ctx context.Context, dynamodbClient int
ConditionExpression: aws.String("attribute_not_exists(compositeKey)"),
}
- res, err := dynamodbClient.PutItem(ctx, input)
+ _, err = dynamodbClient.PutItem(ctx, input)
if err != nil {
- log.Print("hitting error in put item dynamo")
+ log.Print("error inserting item in database")
return nil, err
}
var insertedPurchase internal_types.Purchase
-
- err = attributevalue.UnmarshalMap(res.Attributes, &insertedPurchase)
+ err = attributevalue.UnmarshalMap(item, &insertedPurchase)
if err != nil {
return nil, err
}
diff --git a/functions/gateway/services/dynamodb_service/registration_service.go b/functions/gateway/services/dynamodb_service/registration_service.go
deleted file mode 100644
index e6a2cb66..00000000
--- a/functions/gateway/services/dynamodb_service/registration_service.go
+++ /dev/null
@@ -1,272 +0,0 @@
-package dynamodb_service
-
-import (
- "context"
- "fmt"
- "log"
- "strconv"
- "strings"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
- "github.com/aws/aws-sdk-go-v2/service/dynamodb"
- dynamodb_types "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
- "github.com/meetnearme/api/functions/gateway/helpers"
- internal_types "github.com/meetnearme/api/functions/gateway/types"
-)
-
-var registrationTableName = helpers.GetDbTableName(helpers.RegistrationsTablePrefix)
-
-func init() {
- registrationTableName = helpers.GetDbTableName(helpers.RegistrationsTablePrefix)
-}
-
-// RegistrationService is the concrete implementation of the RegistrationServiceInterface.
-type RegistrationService struct{}
-
-func NewRegistrationService() internal_types.RegistrationServiceInterface {
- return &RegistrationService{}
-}
-
-func (s *RegistrationService) InsertRegistration(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, registration internal_types.RegistrationInsert, eventId, userId string) (*internal_types.Registration, error) {
- // Validate the registration object
- if err := validate.Struct(registration); err != nil {
- return nil, fmt.Errorf("validation failed: %w", err)
- }
-
- // Handle anonymous users by prefixing with unix timestamp
- if userId == "anonymous" {
- userId = fmt.Sprintf("%d-anonymous", time.Now().Unix())
- }
-
- // Update the userId in the registration object before marshaling
- registration.UserId = userId
-
- if registration.CreatedAt.IsZero() {
- registration.CreatedAt = time.Now()
- }
-
- item, err := attributevalue.MarshalMap(®istration)
- if err != nil {
- return nil, err
- }
-
- if registrationTableName == "" {
- return nil, fmt.Errorf("ERR: registrationTableName is empty")
- }
-
- input := &dynamodb.PutItemInput{
- Item: item,
- TableName: aws.String(registrationTableName),
- ConditionExpression: aws.String("attribute_not_exists(eventId) AND attribute_not_exists(userId)"),
- }
-
- res, err := dynamodbClient.PutItem(ctx, input)
- if err != nil {
- log.Print("htting error in put item dynamo")
- return nil, err
- }
-
- var insertedRegistration internal_types.Registration
-
- err = attributevalue.UnmarshalMap(res.Attributes, &insertedRegistration)
- if err != nil {
- return nil, err
- }
-
- return &insertedRegistration, nil
-}
-
-func (s *RegistrationService) GetRegistrationByPk(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.Registration, error) {
- input := &dynamodb.GetItemInput{
- TableName: aws.String(registrationTableName),
- Key: map[string]dynamodb_types.AttributeValue{
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- }
-
- result, err := dynamodbClient.GetItem(ctx, input)
- if err != nil {
- return nil, err
- }
-
- var registration internal_types.Registration
- err = attributevalue.UnmarshalMap(result.Item, ®istration)
- if err != nil {
- return nil, err
- }
-
- return ®istration, nil
-}
-
-func (s *RegistrationService) GetRegistrationsByEventID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string, limit int32, startKey string) ([]internal_types.Registration, map[string]dynamodb_types.AttributeValue, error) {
- queryInput := &dynamodb.QueryInput{
- TableName: aws.String(registrationTableName),
- Limit: aws.Int32(limit),
- KeyConditions: map[string]dynamodb_types.Condition{
- "eventId": {
- ComparisonOperator: dynamodb_types.ComparisonOperatorEq,
- AttributeValueList: []dynamodb_types.AttributeValue{
- &dynamodb_types.AttributeValueMemberS{Value: eventId},
- },
- },
- },
- }
-
- // If startKey is provided, use it for pagination
- if startKey != "" {
- // Extract createdAtString from the composite key (value after second '_')
- parts := strings.Split(startKey, "_")
- if len(parts) != 2 {
- return nil, nil, fmt.Errorf("invalid startKey format")
- }
- userId := parts[0]
- eventId := parts[1]
-
- queryInput.ExclusiveStartKey = map[string]dynamodb_types.AttributeValue{
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- }
- }
-
- result, err := dynamodbClient.Query(ctx, queryInput)
- if err != nil {
- return nil, nil, err
- }
-
- var registrations []internal_types.Registration
- err = attributevalue.UnmarshalListOfMaps(result.Items, ®istrations)
- if err != nil {
- return nil, nil, err
- }
-
- return registrations, result.LastEvaluatedKey, nil
-}
-
-func (s *RegistrationService) GetRegistrationsByUserID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string) ([]internal_types.Registration, error) {
- input := &dynamodb.QueryInput{
- TableName: aws.String(registrationTableName),
- IndexName: aws.String("userIdGsi"), // GSI name
- KeyConditionExpression: aws.String("userId = :userId"),
- ExpressionAttributeValues: map[string]dynamodb_types.AttributeValue{
- ":userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- }
-
- result, err := dynamodbClient.Query(context.TODO(), input)
- if err != nil {
- log.Fatalf("Query GSI failed, %v", err)
- }
-
- var registrations []internal_types.Registration
- err = attributevalue.UnmarshalListOfMaps(result.Items, ®istrations)
- if err != nil {
- return nil, err
- }
-
- return registrations, nil
-}
-
-func (s *RegistrationService) UpdateRegistration(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, registration internal_types.RegistrationUpdate) (*internal_types.Registration, error) {
- if registrationTableName == "" {
- return nil, fmt.Errorf("ERR: registrationTableName is empty")
- }
-
- // Build the UpdateItemInput with composite key
- input := &dynamodb.UpdateItemInput{
- TableName: aws.String(registrationTableName),
- Key: map[string]dynamodb_types.AttributeValue{
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- ExpressionAttributeNames: make(map[string]string),
- ExpressionAttributeValues: make(map[string]dynamodb_types.AttributeValue),
- UpdateExpression: aws.String("SET"),
- ReturnValues: dynamodb_types.ReturnValueAllNew,
- }
-
- // Check if responses need to be updated
- if len(registration.Responses) > 0 {
- input.ExpressionAttributeNames["#responses"] = "responses"
- responses, err := attributevalue.MarshalList(registration.Responses)
- if err != nil {
- return nil, err
- }
- input.ExpressionAttributeValues[":responses"] = &dynamodb_types.AttributeValueMemberL{Value: responses}
- *input.UpdateExpression += " #responses = :responses,"
- }
-
- // Set the updatedAt field
- currentTime := time.Now().Unix()
- input.ExpressionAttributeNames["#updatedAt"] = "updatedAt"
- input.ExpressionAttributeValues[":updatedAt"] = &dynamodb_types.AttributeValueMemberN{Value: strconv.FormatInt(currentTime, 10)}
- *input.UpdateExpression += " #updatedAt = :updatedAt"
-
- // Execute the update
- res, err := dynamodbClient.UpdateItem(ctx, input)
- if err != nil {
- return nil, err
- }
-
- // Unmarshal the updated registration
- var updatedRegistration internal_types.Registration
- err = attributevalue.UnmarshalMap(res.Attributes, &updatedRegistration)
- if err != nil {
- return nil, err
- }
-
- return &updatedRegistration, nil
-}
-
-func (s *RegistrationService) DeleteRegistration(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error {
- input := &dynamodb.DeleteItemInput{
- TableName: aws.String(registrationTableName),
- Key: map[string]dynamodb_types.AttributeValue{
- "eventId": &dynamodb_types.AttributeValueMemberS{Value: eventId},
- "userId": &dynamodb_types.AttributeValueMemberS{Value: userId},
- },
- }
-
- _, err := dynamodbClient.DeleteItem(ctx, input)
- if err != nil {
- return err
- }
-
- log.Printf("registration fields successfully deleted")
- return nil
-}
-
-type MockRegistrationService struct {
- InsertRegistrationFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, registration internal_types.RegistrationInsert, eventId, userId string) (*internal_types.Registration, error)
- GetRegistrationByPkFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.Registration, error)
- GetRegistrationsByEventIDFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string, limit int32, startKey string) ([]internal_types.Registration, map[string]dynamodb_types.AttributeValue, error)
- GetRegistrationsByUserIDFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string) ([]internal_types.Registration, error)
- UpdateRegistrationFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, registration internal_types.RegistrationUpdate) (*internal_types.Registration, error)
- DeleteRegistrationFunc func(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error
-}
-
-func (m *MockRegistrationService) InsertRegistration(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, registration internal_types.RegistrationInsert, eventId, userId string) (*internal_types.Registration, error) {
- return m.InsertRegistrationFunc(ctx, dynamodbClient, registration, eventId, userId)
-}
-
-func (m *MockRegistrationService) GetRegistrationByPk(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) (*internal_types.Registration, error) {
- return m.GetRegistrationByPkFunc(ctx, dynamodbClient, eventId, userId)
-}
-
-func (m *MockRegistrationService) GetRegistrationsByEventID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId string, limit int32, startKey string) ([]internal_types.Registration, map[string]dynamodb_types.AttributeValue, error) {
- return m.GetRegistrationsByEventIDFunc(ctx, dynamodbClient, eventId, limit, startKey)
-}
-
-func (m *MockRegistrationService) GetRegistrationsByUserID(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, userId string) ([]internal_types.Registration, error) {
- return m.GetRegistrationsByUserIDFunc(ctx, dynamodbClient, userId)
-}
-
-func (m *MockRegistrationService) UpdateRegistration(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string, registration internal_types.RegistrationUpdate) (*internal_types.Registration, error) {
- return m.UpdateRegistrationFunc(ctx, dynamodbClient, eventId, userId, registration)
-}
-
-func (m *MockRegistrationService) DeleteRegistration(ctx context.Context, dynamodbClient internal_types.DynamoDBAPI, eventId, userId string) error {
- return m.DeleteRegistrationFunc(ctx, dynamodbClient, eventId, userId)
-}
diff --git a/functions/gateway/services/dynamodb_service/registration_service_test.go b/functions/gateway/services/dynamodb_service/registration_service_test.go
deleted file mode 100644
index 1a489e78..00000000
--- a/functions/gateway/services/dynamodb_service/registration_service_test.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package dynamodb_service
-
-import (
- "context"
- "testing"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/service/dynamodb"
- "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
- "github.com/meetnearme/api/functions/gateway/test_helpers"
- internal_types "github.com/meetnearme/api/functions/gateway/types"
-)
-
-func TestInsertRegistration(t *testing.T) {
- mockDynamoDBClient := &test_helpers.MockDynamoDBClient{
- PutItemFunc: func(ctx context.Context, input *dynamodb.PutItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error) {
- return &dynamodb.PutItemOutput{}, nil
- },
- }
- service := NewRegistrationService()
-
- now := time.Now()
- registration := internal_types.RegistrationInsert{
- EventId: "eventId",
- UserId: "userId",
- Responses: []map[string]interface{}{
- {"question1": "answer1"},
- },
- CreatedAt: now,
- UpdatedAt: now,
- }
-
- result, err := service.InsertRegistration(context.TODO(), mockDynamoDBClient, registration, registration.EventId, registration.UserId)
-
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
- if result == nil {
- t.Error("expected non-nil result")
- }
-}
-
-func TestGetRegistrationByPk(t *testing.T) {
- mockDynamoDBClient := &test_helpers.MockDynamoDBClient{
- GetItemFunc: func(ctx context.Context, input *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error) {
- return &dynamodb.GetItemOutput{
- Item: map[string]types.AttributeValue{
- "eventId": &types.AttributeValueMemberS{Value: "eventId"},
- "userId": &types.AttributeValueMemberS{Value: "userId"},
- "responses": &types.AttributeValueMemberL{
- Value: []types.AttributeValue{
- &types.AttributeValueMemberM{
- Value: map[string]types.AttributeValue{
- "question1": &types.AttributeValueMemberS{Value: "answer1"},
- },
- },
- },
- },
- "createdAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- "updatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- },
- }, nil
- },
- }
- service := NewRegistrationService()
-
- result, err := service.GetRegistrationByPk(context.TODO(), mockDynamoDBClient, "eventId", "userId")
-
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
- if result == nil {
- t.Error("expected non-nil result")
- }
- // Validate returned fields
- if result.EventId != "eventId" || result.UserId != "userId" || len(result.Responses) == 0 {
- t.Errorf("unexpected result: %+v", result)
- }
-}
-func TestGetRegistrationsByEventID(t *testing.T) {
- t.Run("basic query", func(t *testing.T) {
- mockDynamoDBClient := &test_helpers.MockDynamoDBClient{
- QueryFunc: func(ctx context.Context, input *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) {
- return &dynamodb.QueryOutput{
- Items: []map[string]types.AttributeValue{
- {
- "eventId": &types.AttributeValueMemberS{Value: "eventId"},
- "userId": &types.AttributeValueMemberS{Value: "userId"},
- "responses": &types.AttributeValueMemberL{
- Value: []types.AttributeValue{
- &types.AttributeValueMemberM{
- Value: map[string]types.AttributeValue{
- "question1": &types.AttributeValueMemberS{Value: "answer1"},
- },
- },
- },
- },
- "createdAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- "updatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- },
- },
- }, nil
- },
- }
- service := NewRegistrationService()
-
- results, _, err := service.GetRegistrationsByEventID(context.TODO(), mockDynamoDBClient, "eventId", 100, "")
-
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
- if len(results) == 0 {
- t.Error("expected non-empty results")
- }
- })
-
- t.Run("with pagination", func(t *testing.T) {
- mockDynamoDBClient := &test_helpers.MockDynamoDBClient{
- QueryFunc: func(ctx context.Context, input *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error) {
- // Verify the ExclusiveStartKey was set correctly
- if input.ExclusiveStartKey != nil {
- expectedUserId := "user123"
- expectedEventId := "event456"
- gotUserId := input.ExclusiveStartKey["userId"].(*types.AttributeValueMemberS).Value
- gotEventId := input.ExclusiveStartKey["eventId"].(*types.AttributeValueMemberS).Value
-
- if gotUserId != expectedUserId || gotEventId != expectedEventId {
- t.Errorf("incorrect ExclusiveStartKey values, got userId=%s, eventId=%s, want userId=%s, eventId=%s",
- gotUserId, gotEventId, expectedUserId, expectedEventId)
- }
- }
-
- return &dynamodb.QueryOutput{
- Items: []map[string]types.AttributeValue{
- {
- "eventId": &types.AttributeValueMemberS{Value: "event456"},
- "userId": &types.AttributeValueMemberS{Value: "nextUser"},
- "responses": &types.AttributeValueMemberL{
- Value: []types.AttributeValue{
- &types.AttributeValueMemberM{
- Value: map[string]types.AttributeValue{
- "question1": &types.AttributeValueMemberS{Value: "answer1"},
- },
- },
- },
- },
- "createdAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- "updatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- },
- },
- LastEvaluatedKey: map[string]types.AttributeValue{
- "eventId": &types.AttributeValueMemberS{Value: "event456"},
- "userId": &types.AttributeValueMemberS{Value: "nextUser"},
- },
- }, nil
- },
- }
- service := NewRegistrationService()
-
- startKey := "user123_event456"
- results, lastKey, err := service.GetRegistrationsByEventID(context.TODO(), mockDynamoDBClient, "event456", 100, startKey)
-
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
- if len(results) == 0 {
- t.Error("expected non-empty results")
- }
- if lastKey == nil {
- t.Error("expected LastEvaluatedKey to be present")
- }
- })
-}
-
-func TestUpdateRegistration(t *testing.T) {
- mockDynamoDBClient := &test_helpers.MockDynamoDBClient{
- UpdateItemFunc: func(ctx context.Context, input *dynamodb.UpdateItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.UpdateItemOutput, error) {
- return &dynamodb.UpdateItemOutput{
- Attributes: map[string]types.AttributeValue{
- "eventId": &types.AttributeValueMemberS{Value: "eventId"},
- "userId": &types.AttributeValueMemberS{Value: "userId"},
- "responses": &types.AttributeValueMemberL{
- Value: []types.AttributeValue{
- &types.AttributeValueMemberM{
- Value: map[string]types.AttributeValue{
- "question1": &types.AttributeValueMemberS{Value: "updatedAnswer"},
- },
- },
- },
- },
- "createdAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- "updatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
- },
- }, nil
- },
- }
- service := NewRegistrationService()
-
- updatedRegistration, err := service.UpdateRegistration(context.TODO(), mockDynamoDBClient, "eventId", "userId", internal_types.RegistrationUpdate{
- Responses: []map[string]interface{}{
- {"question1": "updatedAnswer"},
- },
- UpdatedAt: time.Now(),
- })
-
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
- if updatedRegistration == nil {
- t.Error("expected non-nil result")
- }
- // Validate returned fields
- if updatedRegistration.UserId != "userId" || len(updatedRegistration.Responses) == 0 {
- t.Errorf("unexpected result: %+v", updatedRegistration)
- }
-}
-
-func TestDeleteRegistration(t *testing.T) {
- mockDynamoDBClient := &test_helpers.MockDynamoDBClient{
- DeleteItemFunc: func(ctx context.Context, input *dynamodb.DeleteItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error) {
- return &dynamodb.DeleteItemOutput{}, nil
- },
- }
- service := NewRegistrationService()
-
- err := service.DeleteRegistration(context.TODO(), mockDynamoDBClient, "eventId", "userId")
-
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
-}
diff --git a/functions/gateway/templates/components/navbar.templ b/functions/gateway/templates/components/navbar.templ
index 35f0a44e..0370eeec 100644
--- a/functions/gateway/templates/components/navbar.templ
+++ b/functions/gateway/templates/components/navbar.templ
@@ -516,38 +516,23 @@ templ Navbar(userInfo helpers.UserInfo, subnavTabs []string, event types.Event)
console.log(`purchasedItems:`, purchasedItems)
// TODO: do we need the total?
console.log(`total`, total)
-
if (purchasedItems?.length < 1) {
this.errors['checkout'] = "You have no selected items.";
this.reqInFlight = false;
return
}
- let response;
- if (total > 0) {
- response = await fetch(`/api/checkout/${this.eventId}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- event_name: this.eventName,
- purchased_items: purchasedItems,
- total: total,
- currency: "USD"
- })
- });
- } else {
- const regResponses = purchasedItems.flatMap(item => item.reg_responses)
- response = await fetch(`/api/registrations/${this.eventId}/${this.userId || 'anonymous'}`, {
- method: !this.userId ? 'POST' : 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- responses: regResponses,
- })
- });
- }
+ const response = await fetch(`/api/checkout/${this.eventId}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ event_name: this.eventName,
+ purchased_items: purchasedItems,
+ total: total,
+ currency: "USD"
+ })
+ });
if (response.status === 401) {
this.errors['checkout'] = `You must be logged in to purchase tickets.
Register or Login now`;
this.reqInFlight = false;
@@ -565,7 +550,9 @@ templ Navbar(userInfo helpers.UserInfo, subnavTabs []string, event types.Event)
this.errors['checkout'] = "Invalid checkout URL received.";
}
} else if (total <= 0) {
- window.location.href = window.location.origin + window.location.pathname + "?checkout=registered";
+ // `?new_purch_key` is received by the profile page to both highlight, scroll to, and poll in
+ // wait for the new event because of dynamo's eventual consistency.
+ window.location.href = window.location.origin + `/admin/profile#registration-history${this.userId ? `?new_purch_key=${json?.composite_key}` : ''}`;
} else {
if (json?.error?.message) {
this.errors['checkout'] = json.error.message;
diff --git a/functions/gateway/templates/pages/event_add_edit.templ b/functions/gateway/templates/pages/event_add_edit.templ
index fe5d7268..5a6a4607 100644
--- a/functions/gateway/templates/pages/event_add_edit.templ
+++ b/functions/gateway/templates/pages/event_add_edit.templ
@@ -12,7 +12,7 @@ templ AddOrEditEventPage(pageObj helpers.SitePage, event types.Event, isEditor b
diff --git a/functions/gateway/templates/pages/event_attendees.templ b/functions/gateway/templates/pages/event_attendees.templ
index 29fe1d53..148535c6 100644
--- a/functions/gateway/templates/pages/event_attendees.templ
+++ b/functions/gateway/templates/pages/event_attendees.templ
@@ -28,7 +28,7 @@ templ EventAttendeesPage(pageObj helpers.SitePage, event types.Event, isEditor b
-
-
diff --git a/functions/gateway/templates/pages/event_attendees_templ_test.go b/functions/gateway/templates/pages/event_attendees_templ_test.go
index 46756d0e..0e590c19 100644
--- a/functions/gateway/templates/pages/event_attendees_templ_test.go
+++ b/functions/gateway/templates/pages/event_attendees_templ_test.go
@@ -49,26 +49,6 @@ func TestEventAttendeesPage(t *testing.T) {
"data-event-has-registration-fields=\"false\"",
},
},
- {
- name: "Valid event with registration fields",
- pageObj: helpers.SitePage{
- Name: "Event Attendees",
- },
- event: types.Event{
- Id: "test-456",
- Name: "Test Event",
- HasPurchasable: false,
- HasRegistrationFields: true,
- },
- isEditor: true,
- expected: []string{
- "Event Attendees",
- "Registrations",
- "data-event-id=\"test-456\"",
- "data-event-has-purchasable=\"false\"",
- "data-event-has-registration-fields=\"true\"",
- },
- },
{
name: "Valid event with both purchasable and registration fields",
pageObj: helpers.SitePage{
@@ -84,7 +64,6 @@ func TestEventAttendeesPage(t *testing.T) {
expected: []string{
"Event Attendees",
"Purchases",
- "Registrations",
"data-event-id=\"test-789\"",
"data-event-has-purchasable=\"true\"",
"data-event-has-registration-fields=\"true\"",
diff --git a/functions/gateway/templates/pages/event_details.templ b/functions/gateway/templates/pages/event_details.templ
index 2a80da57..2b377ca7 100644
--- a/functions/gateway/templates/pages/event_details.templ
+++ b/functions/gateway/templates/pages/event_details.templ
@@ -38,7 +38,7 @@ templ IconLeftSection(labelText, labelValue, icon, url string, venueSection bool
}
-templ EventDetailsPage(event types.Event, checkoutParamVal string, canEdit bool) {
+templ EventDetailsPage(event types.Event, userInfo helpers.UserInfo, checkoutParamVal string, canEdit bool) {
if event.Id == "" {
@@ -58,7 +58,9 @@ templ EventDetailsPage(event types.Event, checkoutParamVal string, canEdit bool)
} else {
if (canEdit) {
-
+
}
{ event.Name }
if event.EventSourceType == helpers.ES_SERIES_PARENT {
@@ -108,9 +110,9 @@ templ EventDetailsPage(event types.Event, checkoutParamVal string, canEdit bool)
- if event.HasPurchasable {
+ if event.StartingPrice > 0 {
- } else if event.HasRegistrationFields {
+ } else if event.HasRegistrationFields && event.StartingPrice == 0 {
} else {
RSVP
+ >
+ if userInfo.Sub == "" {
+ ADD TO CALENDAR
+ } else {
+ RSVP
+ }
+
+ }
+ if userInfo.Sub != "" {
+
+
+
+
}
-
-
-
-
}
-
diff --git a/functions/gateway/templates/pages/profile.templ b/functions/gateway/templates/pages/profile.templ
index 492ce9c0..e9390023 100644
--- a/functions/gateway/templates/pages/profile.templ
+++ b/functions/gateway/templates/pages/profile.templ
@@ -12,7 +12,98 @@ templ ProfilePage(userInfo helpers.UserInfo, roleClaims []helpers.RoleClaim, int
@components.ProfileNav()
-
My Info
+
Event Purchases & Registrations
+
+
+ Show Next →
+
+
+
+
+ Status |
+ Purchase Type(s) |
+ Total |
+ Signup Time |
+
+
+
+
+
+ |
+
+
+ |
+ |
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ Registration Responses
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+
+
+ Status |
+ Purchase Type(s) |
+ Total |
+ Time |
+
+
+
+
+
My Info
Name: { userInfo.Name }
Email: { userInfo.Email }
@@ -71,8 +162,7 @@ templ ProfilePage(userInfo helpers.UserInfo, roleClaims []helpers.RoleClaim, int
-
-
Events Owned by Me
+
Events Owned by Me
// TODO: this is fake data, delete
@@ -81,11 +171,10 @@ templ ProfilePage(userInfo helpers.UserInfo, roleClaims []helpers.RoleClaim, int
Date |
Time |
Location |
- |
- Add Your Own Events Soon
+ Add Your Own Events Soon
//
//
//
@@ -155,13 +244,77 @@ templ ProfilePage(userInfo helpers.UserInfo, roleClaims []helpers.RoleClaim, int
|
-
+ return {
+ init() {
+ (async () => {
+ window.location.href.match('#')
+ let searchQuery = window.location.hash.split('?')[1] || '';
+ if (!window.location.href.match('#')) {
+ searchQuery = window.location.search
+ }
+ this.newKeyId = new URLSearchParams(searchQuery).get('new_purch_key');
+ const maxAttempts = 10;
+ const delayMs = 250;
+ let attempts = 0;
+
+ const fetchPurchases = async () => {
+ const startKey = new URLSearchParams(window.location.search).get('purch_start_key') ?? '';
+ const reqUrl = `/api/purchases/user/${this.userId}${ startKey ? `?start_key=${encodeURIComponent(startKey)}` : '' }`;
+ const purchasesResponse = await fetch(reqUrl);
+ const purchasesResData = await purchasesResponse.json();
+ this.purchases = purchasesResData.purchases ?? [];
+ this.purchasesNextCursor = purchasesResData?.nextKey?.compositeKey?.Value;
+ this.hasPurchasesResults = true;
+ };
+
+ const poll = async () => {
+ try {
+ await fetchPurchases();
+
+ // If we're not looking for a specific purchase, or if we found it, stop polling
+ if (!this.newKeyId || this.purchases.some(p => p.composite_key === this.newKeyId)) {
+ if (this.purchases.some(p => p.composite_key === this.newKeyId)) {
+ const purchaseRow = document.querySelector(`[data-purch-key="${this.newKeyId}"]`);
+ if (purchaseRow) {
+ purchaseRow.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
+ }
+ }
+ return;
+ }
+
+ // Continue polling if we haven't reached max attempts
+ if (attempts < maxAttempts) {
+ attempts++;
+ await new Promise(resolve => setTimeout(resolve, delayMs));
+ await poll();
+ }
+ } catch (error) {
+ console.error('Failed to fetch purchases:', error);
+ }
+ };
+
+ await poll();
+ })()
+ },
+ newKeyId: null,
+ purchases: [],
+ hasPurchasesResults: false,
+ purchasesNextCursor: null,
+ userId: document.querySelector('#profile-state').getAttribute('data-user-id'),
+ statusPending: document.querySelector('#profile-state').getAttribute('data-status-pending'),
+ statusSettled: document.querySelector('#profile-state').getAttribute('data-status-settled'),
+ getPurchasesNextLink() {
+ return window.location.href + '?purch_start_key=' + this.purchasesNextCursor + '_' + this.userId;
+ },
+ handleSubdomainPostRes: function(event) {
+ console.log(event)
+ },
+ getPurchaseStatus(purchase) {
+ return purchase.status.replace(this.statusSettled, 'PAID').replace(this.statusPending, 'INCOMPLETE')
+ }
+ }
+ }
+
}
diff --git a/functions/gateway/types/event_rsvps.go b/functions/gateway/types/event_rsvps.go
deleted file mode 100644
index 758bbb67..00000000
--- a/functions/gateway/types/event_rsvps.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package types
-
-import (
- "context"
- "time"
-)
-
-// EventRsvpsInsert represents the data required to insert a new user
-type EventRsvpInsert struct {
- UserID string `json:"user_id" validate:"required" dynamodbav:"userId"`
- EventID string `json:"event_id" validate:"required" dynamodbav:"eventId"`
- EventSourceType string `json:"event_source_type" validate:"required" dynamodbav:"eventSourceType"` // Validate as email
- EventSourceID string `json:"event_source_id" validate:"required" dynamodbav:"eventSourceId"` // Validate as email
- Status string `json:"status" validate:"required" dynamodbav:"status"`
- CreatedAt time.Time `json:"created_at" dynamodbav:"createdAt"` // Adjust based on your date format
- UpdatedAt time.Time `json:"updated_at" dynamodbav:"updatedAt"` // Adjust based on your date format
-}
-
-
-// EventRsvps represents a user in the system
-type EventRsvp struct {
- UserID string `json:"user_id" dynamodbav:"userId"`
- EventID string `json:"event_id" dynamodbav:"eventId"`
- EventSourceType string `json:"event_source_type" dynamodbav:"eventSourceType"` // Validate as email
- EventSourceID string `json:"event_source_id" dynamodbav:"eventSourceId"` // Validate as email
- Status string `json:"status" dynamodbav:"status"`
- CreatedAt time.Time `json:"created_at" dynamodbav:"createdAt"` // Adjust based on your date format
- UpdatedAt time.Time `json:"updated_at" dynamodbav:"updatedAt"` // Adjust based on your date format
-}
-
-// EventRsvpsUpdate represents the data required to update a user
-type EventRsvpUpdate struct {
- UserID string `json:"user_id" dynamodbav:"userId"`
- EventID string `json:"event_id" dynamodbav:"eventId"`
- EventSourceID string `json:"event_source_id" dynamodbav:"eventSourceId"` // Validate as email
- EventSourceType string `json:"event_source_type" dynamodbav:"eventSourceType"` // Validate as email
- Status string `json:"status" dynamodbav:"status"`
- UpdatedAt time.Time `json:"updated_at" dynamodbav:"updatedAt"`
-}
-
-// EventRsvpsServiceInterface defines the methods for user-related operations using the RDSDataAPI
-type EventRsvpServiceInterface interface {
- InsertEventRsvp(ctx context.Context, dynamodbClient DynamoDBAPI, eventRsvp EventRsvpInsert) (*EventRsvp, error)
- GetEventRsvpByPk(ctx context.Context, dynamodbClient DynamoDBAPI, eventId, userId string) (*EventRsvp, error)
- GetEventRsvpsByUserID(ctx context.Context, dynamodbClient DynamoDBAPI, userId string) ([]EventRsvp, error)
- GetEventRsvpsByEventID(ctx context.Context, dynamodbClient DynamoDBAPI, eventId string) ([]EventRsvp, error)
- UpdateEventRsvp(ctx context.Context, dynamodbClient DynamoDBAPI, eventId, userId string, eventRsvp EventRsvpUpdate) (*EventRsvp, error)
- DeleteEventRsvp(ctx context.Context, dynamodbClient DynamoDBAPI, eventId, userId string) error
-}
-
-
-
diff --git a/functions/gateway/types/purchases.go b/functions/gateway/types/purchases.go
index 819fb5d3..f5321709 100644
--- a/functions/gateway/types/purchases.go
+++ b/functions/gateway/types/purchases.go
@@ -30,8 +30,8 @@ type PurchaseInsert struct {
EventName string `json:"event_name" validate:"required" dynamodbav:"eventName"`
Status string `json:"status" validate:"required" dynamodbav:"status"`
PurchasedItems []PurchasedItem `json:"purchased_items" validate:"required" dynamodbav:"purchasedItems"`
- Total int32 `json:"total" validate:"required" dynamodbav:"total"`
- Currency string `json:"currency" validate:"required" dynamodbav:"currency"`
+ Total int32 `json:"total" dynamodbav:"total"`
+ Currency string `json:"currency" dynamodbav:"currency"`
StripeSessionId string `json:"stripe_session_id" dynamodbav:"stripeSessionId"`
StripeTransactionId string `json:"stripe_transaction_id" dynamodbav:"stripeTransactionId"`
CreatedAt int64 `json:"created_at" validate:"required" dynamodbav:"createdAt"` // Adjust based on your date format
diff --git a/functions/gateway/types/registrations.go b/functions/gateway/types/registrations.go
deleted file mode 100644
index a7d446b8..00000000
--- a/functions/gateway/types/registrations.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package types
-
-import (
- "context"
- "time"
-
- dynamodb_types "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
-)
-
-// RegistrationsInsert represents the data required to insert a new user
-type RegistrationInsert struct {
- EventId string `json:"event_id" validate:"required" dynamodbav:"eventId"` // UUID format validation
- UserId string `json:"user_id" validate:"required" dynamodbav:"userId"`
- Responses []map[string]interface{} `json:"responses" dynamodbav:"responses"`
- CreatedAt time.Time `json:"created_at" dynamodbav:"createdAt"` // Adjust based on your date format
- UpdatedAt time.Time `json:"updated_at" dynamodbav:"updatedAt"` // Adjust based on your date format
-}
-
-// Registrations represents a user in the system
-type Registration struct {
- EventId string `json:"event_id" validate:"required" dynamodbav:"eventId` // UUID format validation
- UserId string `json:"user_id" validate:"required" dynamodbav:"userId"`
- Responses []map[string]interface{} `json:"responses" dynamodbav:"responses"`
- CreatedAt time.Time `json:"created_at" dynamodbav:"createdAt"` // Adjust based on your date format
- UpdatedAt time.Time `json:"updated_at" dynamodbav:"updatedAt"` // Adjust based on your date format
-}
-
-// RegistrationsUpdate represents the data required to update a user
-type RegistrationUpdate struct {
- EventId string `json:"event_id" validate:"required" dynamodbav:"eventId` // UUID format validation
- UserId string `json:"user_id" validate:"required" dynamodbav:"userId"`
- Responses []map[string]interface{} `json:"responses" dynamodbav:"responses"`
- UpdatedAt time.Time `json:"updated_at" dynamodbav:"updatedAt"` // Adjust based on your date format
-}
-
-// RegistrationsServiceInterface defines the methods for user-related operations using the RDSDataAPI
-type RegistrationServiceInterface interface {
- InsertRegistration(ctx context.Context, dynamoClient DynamoDBAPI, registration RegistrationInsert, eventId, userId string) (*Registration, error)
- GetRegistrationByPk(ctx context.Context, dynamoClient DynamoDBAPI, eventId, userId string) (*Registration, error)
- GetRegistrationsByUserID(ctx context.Context, dynamoClient DynamoDBAPI, userId string) ([]Registration, error)
- GetRegistrationsByEventID(ctx context.Context, dynamoClient DynamoDBAPI, eventId string, limit int32, startKey string) ([]Registration, map[string]dynamodb_types.AttributeValue, error)
- UpdateRegistration(ctx context.Context, dynamoClient DynamoDBAPI, eventId, userId string, registration RegistrationUpdate) (*Registration, error)
- DeleteRegistration(ctx context.Context, dynamoClient DynamoDBAPI, eventId, userId string) error
-}
diff --git a/package.json b/package.json
index 4b877fc0..76db92bd 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"deploy:prod": "sst deploy --stage prod",
"remove": "sst remove",
"console": "sst console",
- "tailwind:prod": "tailwindcss -i ./static/assets/global.css -o ./static/assets/styles.css --minify",
+ "tailwind:prod": "NODE_ENV=production tailwindcss --postcss -i ./static/assets/global.css -o ./static/assets/styles.css --minify",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
diff --git a/postcss.config.js b/postcss.config.js
index 6cfe9c10..7f838842 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -6,71 +6,99 @@ import path from 'path';
let previousHash = '';
let previousTemplateHash = '';
+function updateCSSFiles(css, result) {
+ const resultString = JSON.stringify(result.messages);
+ const combinedContent = css + resultString;
+ const newHash = crypto.createHash('md5').update(combinedContent).digest('hex').slice(0, 8);
+
+ // Define paths
+ const baseStylesPath = './static/assets/styles';
+ const tempFile = `${baseStylesPath}.css`;
+
+ // Add initial file creation
+ if (!fs.existsSync(tempFile)) {
+ console.log('Creating initial styles.css file');
+ fs.writeFileSync(tempFile, css);
+ }
+
+ const newFileName = `${baseStylesPath}.${newHash}.css`;
+ const templatePath = 'functions/gateway/templates/pages/layout.templ';
+ // Always copy the file in production mode
+ if (process.env.NODE_ENV === 'production') {
+ if (fs.existsSync(tempFile)) {
+ fs.copyFileSync(tempFile, newFileName);
+
+ // Update template with new hash
+ if (fs.existsSync(templatePath)) {
+ const template = fs.readFileSync(templatePath, 'utf8');
+ const pattern = /styles\..*?\.css/;
+ const updatedTemplate = template.replace(pattern, `styles.${newHash}.css`);
+ fs.writeFileSync(templatePath, updatedTemplate);
+ }
+
+ console.log(`📦 Production CSS generated: ${newFileName}`);
+ return;
+ }
+ }
+
+ // Rest of the watch mode logic
+ try {
+ // Read the template file
+ const template = fs.readFileSync(templatePath, 'utf8');
+
+ // Hash the template content to detect changes
+ const templateHash = crypto.createHash('md5').update(template).digest('hex').slice(0, 8);
+
+ // Extract current hash from filename in template
+ const hashMatch = template.match(/styles\.(.*?)\.css/);
+ const currentHash = hashMatch ? hashMatch[1] : null;
+
+ // Check if we need to update based on:
+ // 1. Hash differences
+ // 2. Missing hashed CSS file
+ // 3. Template content hasn't changed
+ const currentHashedFile = currentHash ? `${baseStylesPath}.${currentHash}.css` : null;
+ const needsUpdate = (newHash !== currentHash && newHash !== previousHash ||
+ (currentHash && !fs.existsSync(currentHashedFile))) &&
+ templateHash !== previousTemplateHash;
+
+ if (needsUpdate) {
+ previousTemplateHash = templateHash; // Store the new template hash
+ const pattern = /styles\..*?\.css/;
+ const updatedTemplate = template.replace(pattern, `styles.${newHash}.css`);
+ fs.writeFileSync(templatePath, updatedTemplate);
+
+ // Ensure the temp file exists and copy it
+ if (fs.existsSync(tempFile)) {
+ fs.copyFileSync(tempFile, newFileName);
+
+ // Remove old CSS file if it exists
+ if (previousHash) {
+ const oldFile = `${baseStylesPath}.${previousHash}.css`;
+ if (fs.existsSync(oldFile)) {
+ fs.unlinkSync(oldFile);
+ }
+ }
+
+ console.log(`🔄 CSS updated: ${newFileName}`);
+ } else {
+ console.warn('Warning: styles.css not found for initial copy');
+ }
+ }
+
+ previousHash = newHash;
+ } catch (error) {
+ console.warn('Warning: Could not update CSS:', error.message);
+ }
+}
+
export default {
plugins: [
tailwindcss,
{
postcssPlugin: 'css-watch-logger',
Once(root, { result }) {
- try {
- const css = root.toString();
- const resultString = JSON.stringify(result.messages);
- const combinedContent = css + resultString;
- const newHash = crypto.createHash('md5').update(combinedContent).digest('hex').slice(0, 8);
-
- // Define paths
- const baseStylesPath = './static/assets/styles';
- const tempFile = `${baseStylesPath}.css`;
- const newFileName = `${baseStylesPath}.${newHash}.css`;
-
- // Read the template file
- const templatePath = 'functions/gateway/templates/pages/layout.templ';
- const template = fs.readFileSync(templatePath, 'utf8');
-
- // Hash the template content to detect changes
- const templateHash = crypto.createHash('md5').update(template).digest('hex').slice(0, 8);
-
- // Extract current hash from filename in template
- const hashMatch = template.match(/styles\.(.*?)\.css/);
- const currentHash = hashMatch ? hashMatch[1] : null;
-
- // Check if we need to update based on:
- // 1. Hash differences
- // 2. Missing hashed CSS file
- // 3. Template content hasn't changed
- const currentHashedFile = currentHash ? `${baseStylesPath}.${currentHash}.css` : null;
- const needsUpdate = (newHash !== currentHash && newHash !== previousHash ||
- (currentHash && !fs.existsSync(currentHashedFile))) &&
- templateHash !== previousTemplateHash;
-
- if (needsUpdate) {
- previousTemplateHash = templateHash; // Store the new template hash
- const pattern = /styles\..*?\.css/;
- const updatedTemplate = template.replace(pattern, `styles.${newHash}.css`);
- fs.writeFileSync(templatePath, updatedTemplate);
-
- // Ensure the temp file exists and copy it
- if (fs.existsSync(tempFile)) {
- fs.copyFileSync(tempFile, newFileName);
-
- // Remove old CSS file if it exists
- if (previousHash) {
- const oldFile = `${baseStylesPath}.${previousHash}.css`;
- if (fs.existsSync(oldFile)) {
- fs.unlinkSync(oldFile);
- }
- }
-
- console.log(`🔄 CSS updated: ${newFileName}`);
- } else {
- console.warn('Warning: styles.css not found for initial copy');
- }
- }
-
- previousHash = newHash;
- } catch (error) {
- console.warn('Warning: Could not update CSS:', error.message);
- }
+ updateCSSFiles(root.toString(), result);
}
}
]
diff --git a/stacks/StaticSiteStack.ts b/stacks/StaticSiteStack.ts
index cfe26a79..eb0ee137 100644
--- a/stacks/StaticSiteStack.ts
+++ b/stacks/StaticSiteStack.ts
@@ -11,7 +11,7 @@ export function StaticSiteStack({ stack }: StackContext) {
dev: {
deploy: true,
},
- buildCommand: 'npm run tailwind:prod',
+ buildCommand: 'NODE_ENV=production npm run tailwind:prod',
});
stack.addOutputs({
StaticEndpoint: staticSite?.url,
diff --git a/stacks/StorageStack.ts b/stacks/StorageStack.ts
index e7dfdbaf..3f63a607 100644
--- a/stacks/StorageStack.ts
+++ b/stacks/StorageStack.ts
@@ -1,8 +1,7 @@
import { StackContext, Table } from 'sst/constructs';
export function StorageStack({ stack }: StackContext) {
- // Create the `Registrations` table
- //
+ // 🚨 WARNING 🚨 Deprecated, do not use
const eventRsvpsTable = new Table(stack, 'EventRsvps', {
fields: {
id: 'string',
@@ -72,6 +71,7 @@ export function StorageStack({ stack }: StackContext) {
primaryIndex: { partitionKey: 'eventId' },
});
+ // 🚨 WARNING 🚨 Deprecated, do not use
const registrationsTable = new Table(stack, 'Registrations', {
fields: {
eventId: 'string',
diff --git a/static/assets/global.css b/static/assets/global.css
index f8bafc32..9439303e 100644
--- a/static/assets/global.css
+++ b/static/assets/global.css
@@ -146,8 +146,9 @@ body:has(.bottom-drawer) {
display: none;
}
-.margins-when-children.my-8:has(*) {
- margin: 2rem 0;
+.margins-when-children:not(:has(:first-child)) {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
}
/* BEGIN seshu ingestion "add event source" section */
diff --git a/static/assets/styles.59a94be0.css b/static/assets/styles.59a94be0.css
deleted file mode 100644
index 272d1984..00000000
--- a/static/assets/styles.59a94be0.css
+++ /dev/null
@@ -1,5871 +0,0 @@
-/*
-! tailwindcss v3.4.9 | MIT License | https://tailwindcss.com
-*/
-
-/*
-1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
-2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
-*/
-
-*,
-::before,
-::after {
- box-sizing: border-box;
- /* 1 */
- border-width: 0;
- /* 2 */
- border-style: solid;
- /* 2 */
- border-color: #e5e7eb;
- /* 2 */
-}
-
-::before,
-::after {
- --tw-content: '';
-}
-
-/*
-1. Use a consistent sensible line-height in all browsers.
-2. Prevent adjustments of font size after orientation changes in iOS.
-3. Use a more readable tab size.
-4. Use the user's configured `sans` font-family by default.
-5. Use the user's configured `sans` font-feature-settings by default.
-6. Use the user's configured `sans` font-variation-settings by default.
-7. Disable tap highlights on iOS
-*/
-
-html,
-:host {
- line-height: 1.5;
- /* 1 */
- -webkit-text-size-adjust: 100%;
- /* 2 */
- -moz-tab-size: 4;
- /* 3 */
- -o-tab-size: 4;
- tab-size: 4;
- /* 3 */
- font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- /* 4 */
- font-feature-settings: normal;
- /* 5 */
- font-variation-settings: normal;
- /* 6 */
- -webkit-tap-highlight-color: transparent;
- /* 7 */
-}
-
-/*
-1. Remove the margin in all browsers.
-2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
-*/
-
-body {
- margin: 0;
- /* 1 */
- line-height: inherit;
- /* 2 */
-}
-
-/*
-1. Add the correct height in Firefox.
-2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
-3. Ensure horizontal rules are visible by default.
-*/
-
-hr {
- height: 0;
- /* 1 */
- color: inherit;
- /* 2 */
- border-top-width: 1px;
- /* 3 */
-}
-
-/*
-Add the correct text decoration in Chrome, Edge, and Safari.
-*/
-
-abbr:where([title]) {
- -webkit-text-decoration: underline dotted;
- text-decoration: underline dotted;
-}
-
-/*
-Remove the default font size and weight for headings.
-*/
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-size: inherit;
- font-weight: inherit;
-}
-
-/*
-Reset links to optimize for opt-in styling instead of opt-out.
-*/
-
-a {
- color: inherit;
- text-decoration: inherit;
-}
-
-/*
-Add the correct font weight in Edge and Safari.
-*/
-
-b,
-strong {
- font-weight: bolder;
-}
-
-/*
-1. Use the user's configured `mono` font-family by default.
-2. Use the user's configured `mono` font-feature-settings by default.
-3. Use the user's configured `mono` font-variation-settings by default.
-4. Correct the odd `em` font sizing in all browsers.
-*/
-
-code,
-kbd,
-samp,
-pre {
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- /* 1 */
- font-feature-settings: normal;
- /* 2 */
- font-variation-settings: normal;
- /* 3 */
- font-size: 1em;
- /* 4 */
-}
-
-/*
-Add the correct font size in all browsers.
-*/
-
-small {
- font-size: 80%;
-}
-
-/*
-Prevent `sub` and `sup` elements from affecting the line height in all browsers.
-*/
-
-sub,
-sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
-}
-
-sub {
- bottom: -0.25em;
-}
-
-sup {
- top: -0.5em;
-}
-
-/*
-1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
-2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
-3. Remove gaps between table borders by default.
-*/
-
-table {
- text-indent: 0;
- /* 1 */
- border-color: inherit;
- /* 2 */
- border-collapse: collapse;
- /* 3 */
-}
-
-/*
-1. Change the font styles in all browsers.
-2. Remove the margin in Firefox and Safari.
-3. Remove default padding in all browsers.
-*/
-
-button,
-input,
-optgroup,
-select,
-textarea {
- font-family: inherit;
- /* 1 */
- font-feature-settings: inherit;
- /* 1 */
- font-variation-settings: inherit;
- /* 1 */
- font-size: 100%;
- /* 1 */
- font-weight: inherit;
- /* 1 */
- line-height: inherit;
- /* 1 */
- letter-spacing: inherit;
- /* 1 */
- color: inherit;
- /* 1 */
- margin: 0;
- /* 2 */
- padding: 0;
- /* 3 */
-}
-
-/*
-Remove the inheritance of text transform in Edge and Firefox.
-*/
-
-button,
-select {
- text-transform: none;
-}
-
-/*
-1. Correct the inability to style clickable types in iOS and Safari.
-2. Remove default button styles.
-*/
-
-button,
-input:where([type='button']),
-input:where([type='reset']),
-input:where([type='submit']) {
- -webkit-appearance: button;
- /* 1 */
- background-color: transparent;
- /* 2 */
- background-image: none;
- /* 2 */
-}
-
-/*
-Use the modern Firefox focus style for all focusable elements.
-*/
-
-:-moz-focusring {
- outline: auto;
-}
-
-/*
-Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
-*/
-
-:-moz-ui-invalid {
- box-shadow: none;
-}
-
-/*
-Add the correct vertical alignment in Chrome and Firefox.
-*/
-
-progress {
- vertical-align: baseline;
-}
-
-/*
-Correct the cursor style of increment and decrement buttons in Safari.
-*/
-
-::-webkit-inner-spin-button,
-::-webkit-outer-spin-button {
- height: auto;
-}
-
-/*
-1. Correct the odd appearance in Chrome and Safari.
-2. Correct the outline style in Safari.
-*/
-
-[type='search'] {
- -webkit-appearance: textfield;
- /* 1 */
- outline-offset: -2px;
- /* 2 */
-}
-
-/*
-Remove the inner padding in Chrome and Safari on macOS.
-*/
-
-::-webkit-search-decoration {
- -webkit-appearance: none;
-}
-
-/*
-1. Correct the inability to style clickable types in iOS and Safari.
-2. Change font properties to `inherit` in Safari.
-*/
-
-::-webkit-file-upload-button {
- -webkit-appearance: button;
- /* 1 */
- font: inherit;
- /* 2 */
-}
-
-/*
-Add the correct display in Chrome and Safari.
-*/
-
-summary {
- display: list-item;
-}
-
-/*
-Removes the default spacing and border for appropriate elements.
-*/
-
-blockquote,
-dl,
-dd,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-hr,
-figure,
-p,
-pre {
- margin: 0;
-}
-
-fieldset {
- margin: 0;
- padding: 0;
-}
-
-legend {
- padding: 0;
-}
-
-ol,
-ul,
-menu {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-/*
-Reset default styling for dialogs.
-*/
-
-dialog {
- padding: 0;
-}
-
-/*
-Prevent resizing textareas horizontally by default.
-*/
-
-textarea {
- resize: vertical;
-}
-
-/*
-1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
-2. Set the default placeholder color to the user's configured gray 400 color.
-*/
-
-input::-moz-placeholder, textarea::-moz-placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
-}
-
-input::placeholder,
-textarea::placeholder {
- opacity: 1;
- /* 1 */
- color: #9ca3af;
- /* 2 */
-}
-
-/*
-Set the default cursor for buttons.
-*/
-
-button,
-[role="button"] {
- cursor: pointer;
-}
-
-/*
-Make sure disabled buttons don't get the pointer cursor.
-*/
-
-:disabled {
- cursor: default;
-}
-
-/*
-1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
-2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
- This can trigger a poorly considered lint error in some tools but is included by design.
-*/
-
-img,
-svg,
-video,
-canvas,
-audio,
-iframe,
-embed,
-object {
- display: block;
- /* 1 */
- vertical-align: middle;
- /* 2 */
-}
-
-/*
-Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
-*/
-
-img,
-video {
- max-width: 100%;
- height: auto;
-}
-
-/* Make elements with the HTML hidden attribute stay hidden by default */
-
-[hidden] {
- display: none;
-}
-
-:root,
-[data-theme] {
- background-color: var(--fallback-b1,oklch(var(--b1)/1));
- color: var(--fallback-bc,oklch(var(--bc)/1));
-}
-
-@supports not (color: oklch(0% 0 0)) {
- :root {
- color-scheme: light;
- --fallback-p: #491eff;
- --fallback-pc: #d4dbff;
- --fallback-s: #ff41c7;
- --fallback-sc: #fff9fc;
- --fallback-a: #00cfbd;
- --fallback-ac: #00100d;
- --fallback-n: #2b3440;
- --fallback-nc: #d7dde4;
- --fallback-b1: #ffffff;
- --fallback-b2: #e5e6e6;
- --fallback-b3: #e5e6e6;
- --fallback-bc: #1f2937;
- --fallback-in: #00b3f0;
- --fallback-inc: #000000;
- --fallback-su: #00ca92;
- --fallback-suc: #000000;
- --fallback-wa: #ffc22d;
- --fallback-wac: #000000;
- --fallback-er: #ff6f70;
- --fallback-erc: #000000;
- }
-
- @media (prefers-color-scheme: dark) {
- :root {
- color-scheme: dark;
- --fallback-p: #7582ff;
- --fallback-pc: #050617;
- --fallback-s: #ff71cf;
- --fallback-sc: #190211;
- --fallback-a: #00c7b5;
- --fallback-ac: #000e0c;
- --fallback-n: #2a323c;
- --fallback-nc: #a6adbb;
- --fallback-b1: #1d232a;
- --fallback-b2: #191e24;
- --fallback-b3: #15191e;
- --fallback-bc: #a6adbb;
- --fallback-in: #00b3f0;
- --fallback-inc: #000000;
- --fallback-su: #00ca92;
- --fallback-suc: #000000;
- --fallback-wa: #ffc22d;
- --fallback-wac: #000000;
- --fallback-er: #ff6f70;
- --fallback-erc: #000000;
- }
- }
-}
-
-html {
- -webkit-tap-highlight-color: transparent;
-}
-
-* {
- scrollbar-color: color-mix(in oklch, currentColor 35%, transparent) transparent;
-}
-
-*:hover {
- scrollbar-color: color-mix(in oklch, currentColor 60%, transparent) transparent;
-}
-
-:root {
- color-scheme: dark;
- --animation-btn: 0.25s;
- --animation-input: .2s;
- --btn-focus-scale: 0.95;
- --border-btn: 1px;
- --tab-border: 1px;
- --p: 87.1432% 0.285969 141.530703;
- --s: 66.0199% 0.229356 35.402514;
- --a: 72.8297% 0.197075 351.994708;
- --n: 84.5222% 0 0;
- --nc: 0% 0 0;
- --b1: 0% 0 0;
- --b2: 28.5017% 0 0;
- --b3: 39.0421% 0 0;
- --bc: 97.0151% 0 0;
- font-family: Ubuntu Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
- --rounded-box: 0.5rem;
- --rounded-btn: 0.25rem;
- --rounded-badge: 1rem;
- --tab-radius: 0.25rem;
- --pc: 17.4328% 0.057824 141.37345;
- --sc: 94.9119% 0 0;
- --ac: 14.6655% 0.038503 352.331265;
- --in: 76.888% 0.097944 243.969204;
- --inc: 0% 0 0;
- --su: 83.9524% 0.205841 141.105446;
- --suc: 0% 0 0;
- --wa: 85.3968% 0.140313 79.943272;
- --wac: 0% 0 0;
- --er: 58.5838% 0.222042 17.584628;
- --erc: 0% 0 0;
-}
-
-h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- font-family: 'Outfit', 'Helvetica', 'Arial', 'sans-serif';
- font-weight: 400;
-}
-
-h1.title,
- h2.title,
- h3.title,
- h4.title,
- h5.title,
- h6.title {
- letter-spacing: 0.15rem;
- text-transform: uppercase;
-}
-
-*, ::before, ::after {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-.alert {
- display: grid;
- width: 100%;
- grid-auto-flow: row;
- align-content: flex-start;
- align-items: center;
- justify-items: center;
- gap: 1rem;
- text-align: center;
- border-radius: var(--rounded-box, 1rem);
- border-width: 1px;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
- padding: 1rem;
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --alert-bg: var(--fallback-b2,oklch(var(--b2)/1));
- --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
- background-color: var(--alert-bg);
-}
-
-@media (min-width: 640px) {
- .alert {
- grid-auto-flow: column;
- grid-template-columns: auto minmax(auto,1fr);
- justify-items: start;
- text-align: start;
- }
-}
-
-.avatar {
- position: relative;
- display: inline-flex;
-}
-
-.avatar > div {
- display: block;
- aspect-ratio: 1 / 1;
- overflow: hidden;
-}
-
-.avatar img {
- height: 100%;
- width: 100%;
- -o-object-fit: cover;
- object-fit: cover;
-}
-
-.avatar.placeholder > div {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 200ms;
- height: 1.25rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
- width: -moz-fit-content;
- width: fit-content;
- padding-left: 0.563rem;
- padding-right: 0.563rem;
- border-radius: var(--rounded-badge, 1.9rem);
- border-width: 1px;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
-}
-
-@media (hover:hover) {
- .label a:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- }
-
- .menu li > *:not(ul, .menu-title, details, .btn):active,
-.menu li > *:not(ul, .menu-title, details, .btn).active,
-.menu li > details > summary:active {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
- }
-
- .tab:hover {
- --tw-text-opacity: 1;
- }
-
- .table tr.hover:hover,
- .table tr.hover:nth-child(even):hover {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
- }
-
- .table-zebra tr.hover:hover,
- .table-zebra tr.hover:nth-child(even):hover {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
- }
-}
-
-.btn {
- display: inline-flex;
- height: 3rem;
- min-height: 3rem;
- flex-shrink: 0;
- cursor: pointer;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- border-radius: var(--rounded-btn, 0.5rem);
- border-color: transparent;
- border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity));
- padding-left: 1rem;
- padding-right: 1rem;
- text-align: center;
- font-size: 0.875rem;
- line-height: 1em;
- gap: 0.5rem;
- font-weight: 600;
- text-decoration-line: none;
- transition-duration: 200ms;
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- border-width: var(--border-btn, 1px);
- transition-property: color, background-color, border-color, opacity, box-shadow, transform;
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
- outline-color: var(--fallback-bc,oklch(var(--bc)/1));
- background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity));
- --tw-bg-opacity: 1;
- --tw-border-opacity: 1;
-}
-
-.btn-disabled,
- .btn[disabled],
- .btn:disabled {
- pointer-events: none;
-}
-
-.btn-square {
- height: 3rem;
- width: 3rem;
- padding: 0px;
-}
-
-.btn-circle {
- height: 3rem;
- width: 3rem;
- border-radius: 9999px;
- padding: 0px;
-}
-
-:where(.btn:is(input[type="checkbox"])),
-:where(.btn:is(input[type="radio"])) {
- width: auto;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-}
-
-.btn:is(input[type="checkbox"]):after,
-.btn:is(input[type="radio"]):after {
- --tw-content: attr(aria-label);
- content: var(--tw-content);
-}
-
-.card {
- position: relative;
- display: flex;
- flex-direction: column;
- border-radius: var(--rounded-box, 1rem);
-}
-
-.card:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.card-body {
- display: flex;
- flex: 1 1 auto;
- flex-direction: column;
- padding: var(--padding-card, 2rem);
- gap: 0.5rem;
-}
-
-.card-body :where(p) {
- flex-grow: 1;
-}
-
-.card figure {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.card.image-full {
- display: grid;
-}
-
-.card.image-full:before {
- position: relative;
- content: "";
- z-index: 10;
- border-radius: var(--rounded-box, 1rem);
- --tw-bg-opacity: 1;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- opacity: 0.75;
-}
-
-.card.image-full:before,
- .card.image-full > * {
- grid-column-start: 1;
- grid-row-start: 1;
-}
-
-.card.image-full > figure img {
- height: 100%;
- -o-object-fit: cover;
- object-fit: cover;
-}
-
-.card.image-full > .card-body {
- position: relative;
- z-index: 20;
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
-}
-
-.carousel {
- display: inline-flex;
- overflow-x: scroll;
- scroll-snap-type: x mandatory;
- scroll-behavior: smooth;
- -ms-overflow-style: none;
- scrollbar-width: none;
-}
-
-.carousel-item {
- box-sizing: content-box;
- display: flex;
- flex: none;
- scroll-snap-align: start;
-}
-
-.carousel-start .carousel-item {
- scroll-snap-align: start;
-}
-
-.carousel-center .carousel-item {
- scroll-snap-align: center;
-}
-
-.carousel-end .carousel-item {
- scroll-snap-align: end;
-}
-
-.checkbox {
- flex-shrink: 0;
- --chkbg: var(--fallback-bc,oklch(var(--bc)/1));
- --chkfg: var(--fallback-b1,oklch(var(--b1)/1));
- height: 1.5rem;
- width: 1.5rem;
- cursor: pointer;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- border-radius: var(--rounded-btn, 0.5rem);
- border-width: 1px;
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- --tw-border-opacity: 0.2;
-}
-
-.collapse:not(td):not(tr):not(colgroup) {
- visibility: visible;
-}
-
-.collapse {
- position: relative;
- display: grid;
- overflow: hidden;
- grid-template-rows: auto 0fr;
- transition: grid-template-rows 0.2s;
- width: 100%;
- border-radius: var(--rounded-box, 1rem);
-}
-
-.collapse-title,
-.collapse > input[type="checkbox"],
-.collapse > input[type="radio"],
-.collapse-content {
- grid-column-start: 1;
- grid-row-start: 1;
-}
-
-.collapse > input[type="checkbox"],
-.collapse > input[type="radio"] {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- opacity: 0;
-}
-
-.collapse-content {
- visibility: hidden;
- grid-column-start: 1;
- grid-row-start: 2;
- min-height: 0px;
- transition: visibility 0.2s;
- transition: padding 0.2s ease-out,
- background-color 0.2s ease-out;
- padding-left: 1rem;
- padding-right: 1rem;
- cursor: unset;
-}
-
-.collapse[open],
-.collapse-open,
-.collapse:focus:not(.collapse-close) {
- grid-template-rows: auto 1fr;
-}
-
-.collapse:not(.collapse-close):has(> input[type="checkbox"]:checked),
-.collapse:not(.collapse-close):has(> input[type="radio"]:checked) {
- grid-template-rows: auto 1fr;
-}
-
-.collapse[open] > .collapse-content,
-.collapse-open > .collapse-content,
-.collapse:focus:not(.collapse-close) > .collapse-content,
-.collapse:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-content,
-.collapse:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-content {
- visibility: visible;
- min-height: -moz-fit-content;
- min-height: fit-content;
-}
-
-.divider {
- display: flex;
- flex-direction: row;
- align-items: center;
- align-self: stretch;
- margin-top: 1rem;
- margin-bottom: 1rem;
- height: 1rem;
- white-space: nowrap;
-}
-
-.divider:before,
- .divider:after {
- height: 0.125rem;
- width: 100%;
- flex-grow: 1;
- --tw-content: '';
- content: var(--tw-content);
- background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
-}
-
-.drawer {
- position: relative;
- display: grid;
- grid-auto-columns: max-content auto;
- width: 100%;
-}
-
-.drawer-content {
- grid-column-start: 2;
- grid-row-start: 1;
- min-width: 0px;
-}
-
-.drawer-side {
- pointer-events: none;
- position: fixed;
- inset-inline-start: 0px;
- top: 0px;
- grid-column-start: 1;
- grid-row-start: 1;
- display: grid;
- width: 100%;
- grid-template-columns: repeat(1, minmax(0, 1fr));
- grid-template-rows: repeat(1, minmax(0, 1fr));
- align-items: flex-start;
- justify-items: start;
- overflow-x: hidden;
- overflow-y: hidden;
- overscroll-behavior: contain;
- height: 100vh;
- height: 100dvh;
-}
-
-.drawer-side > .drawer-overlay {
- position: sticky;
- top: 0px;
- place-self: stretch;
- cursor: pointer;
- background-color: transparent;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 200ms;
-}
-
-.drawer-side > * {
- grid-column-start: 1;
- grid-row-start: 1;
-}
-
-.drawer-side > *:not(.drawer-overlay) {
- transition-property: transform;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 300ms;
- will-change: transform;
- transform: translateX(-100%);
-}
-
-[dir="rtl"] .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(100%);
-}
-
-.drawer-toggle {
- position: fixed;
- height: 0px;
- width: 0px;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- opacity: 0;
-}
-
-.drawer-toggle:checked ~ .drawer-side {
- pointer-events: auto;
- visibility: visible;
- overflow-y: auto;
-}
-
-.drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(0%);
-}
-
-.drawer-end {
- grid-auto-columns: auto max-content;
-}
-
-.drawer-end .drawer-toggle ~ .drawer-content {
- grid-column-start: 1;
-}
-
-.drawer-end .drawer-toggle ~ .drawer-side {
- grid-column-start: 2;
- justify-items: end;
-}
-
-.drawer-end .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(100%);
-}
-
-[dir="rtl"] .drawer-end .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(-100%);
-}
-
-.drawer-end .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(0%);
-}
-
-.dropdown {
- position: relative;
- display: inline-block;
-}
-
-.dropdown > *:not(summary):focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.dropdown .dropdown-content {
- position: absolute;
-}
-
-.dropdown:is(:not(details)) .dropdown-content {
- visibility: hidden;
- opacity: 0;
- transform-origin: top;
- --tw-scale-x: .95;
- --tw-scale-y: .95;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 200ms;
-}
-
-.dropdown-end .dropdown-content {
- inset-inline-end: 0px;
-}
-
-.dropdown-left .dropdown-content {
- bottom: auto;
- inset-inline-end: 100%;
- top: 0px;
- transform-origin: right;
-}
-
-.dropdown-right .dropdown-content {
- bottom: auto;
- inset-inline-start: 100%;
- top: 0px;
- transform-origin: left;
-}
-
-.dropdown-bottom .dropdown-content {
- bottom: auto;
- top: 100%;
- transform-origin: top;
-}
-
-.dropdown-top .dropdown-content {
- bottom: 100%;
- top: auto;
- transform-origin: bottom;
-}
-
-.dropdown-end.dropdown-right .dropdown-content {
- bottom: 0px;
- top: auto;
-}
-
-.dropdown-end.dropdown-left .dropdown-content {
- bottom: 0px;
- top: auto;
-}
-
-.dropdown.dropdown-open .dropdown-content,
-.dropdown:not(.dropdown-hover):focus .dropdown-content,
-.dropdown:focus-within .dropdown-content {
- visibility: visible;
- opacity: 1;
-}
-
-@media (hover: hover) {
- .dropdown.dropdown-hover:hover .dropdown-content {
- visibility: visible;
- opacity: 1;
- }
-
- .btm-nav > *.disabled:hover,
- .btm-nav > *[disabled]:hover {
- pointer-events: none;
- --tw-border-opacity: 0;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-bg-opacity: 0.1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-text-opacity: 0.2;
- }
-
- .btn:hover {
- --tw-border-opacity: 1;
- border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn:hover {
- background-color: color-mix(
- in oklab,
- oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%,
- black
- );
- border-color: color-mix(
- in oklab,
- oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%,
- black
- );
- }
- }
-
- @supports not (color: oklch(0% 0 0)) {
- .btn:hover {
- background-color: var(--btn-color, var(--fallback-b2));
- border-color: var(--btn-color, var(--fallback-b2));
- }
- }
-
- .btn.glass:hover {
- --glass-opacity: 25%;
- --glass-border-opacity: 15%;
- }
-
- .btn-ghost:hover {
- border-color: transparent;
- }
-
- @supports (color: oklch(0% 0 0)) {
- .btn-ghost:hover {
- background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
- }
- }
-
- .btn-outline:hover {
- --tw-border-opacity: 1;
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)));
- }
-
- .btn-outline.btn-primary:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-primary:hover {
- background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
- }
- }
-
- .btn-outline.btn-secondary:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-secondary:hover {
- background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
- }
- }
-
- .btn-outline.btn-accent:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-accent:hover {
- background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
- }
- }
-
- .btn-outline.btn-success:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-success:hover {
- background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
- }
- }
-
- .btn-outline.btn-info:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-info:hover {
- background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
- }
- }
-
- .btn-outline.btn-warning:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-warning:hover {
- background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
- }
- }
-
- .btn-outline.btn-error:hover {
- --tw-text-opacity: 1;
- color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-error:hover {
- background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
- }
- }
-
- .btn-disabled:hover,
- .btn[disabled]:hover,
- .btn:disabled:hover {
- --tw-border-opacity: 0;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-bg-opacity: 0.2;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-text-opacity: 0.2;
- }
-
- @supports (color: color-mix(in oklab, black, black)) {
- .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover {
- background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
- }
- }
-
- .dropdown.dropdown-hover:hover .dropdown-content {
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- }
-
- :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover {
- cursor: pointer;
- outline: 2px solid transparent;
- outline-offset: 2px;
- }
-
- @supports (color: oklch(0% 0 0)) {
- :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover {
- background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
- }
- }
-
- .tab[disabled],
- .tab[disabled]:hover {
- cursor: not-allowed;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-text-opacity: 0.2;
- }
-}
-
-.dropdown:is(details) summary::-webkit-details-marker {
- display: none;
-}
-
-.footer {
- display: grid;
- width: 100%;
- grid-auto-flow: row;
- place-items: start;
- -moz-column-gap: 1rem;
- column-gap: 1rem;
- row-gap: 2.5rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.footer > * {
- display: grid;
- place-items: start;
- gap: 0.5rem;
-}
-
-.footer-center {
- place-items: center;
- text-align: center;
-}
-
-.footer-center > * {
- place-items: center;
-}
-
-@media (min-width: 48rem) {
- .footer {
- grid-auto-flow: column;
- }
-
- .footer-center {
- grid-auto-flow: row dense;
- }
-}
-
-.form-control {
- display: flex;
- flex-direction: column;
-}
-
-.label {
- display: flex;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- align-items: center;
- justify-content: space-between;
- padding-left: 0.25rem;
- padding-right: 0.25rem;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-
-.indicator {
- position: relative;
- display: inline-flex;
- width: -moz-max-content;
- width: max-content;
-}
-
-.indicator :where(.indicator-item) {
- z-index: 1;
- position: absolute;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- white-space: nowrap;
-}
-
-.input {
- flex-shrink: 1;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- height: 3rem;
- padding-left: 1rem;
- padding-right: 1rem;
- font-size: 1rem;
- line-height: 2;
- line-height: 1.5rem;
- border-radius: var(--rounded-btn, 0.5rem);
- border-width: 1px;
- border-color: transparent;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
-
-.input[type="number"]::-webkit-inner-spin-button,
-.input-md[type="number"]::-webkit-inner-spin-button {
- margin-top: -1rem;
- margin-bottom: -1rem;
- margin-inline-end: -1rem;
-}
-
-.join {
- display: inline-flex;
- align-items: stretch;
- border-radius: var(--rounded-btn, 0.5rem);
-}
-
-.join :where(.join-item) {
- border-start-end-radius: 0;
- border-end-end-radius: 0;
- border-end-start-radius: 0;
- border-start-start-radius: 0;
-}
-
-.join .join-item:not(:first-child):not(:last-child),
- .join *:not(:first-child):not(:last-child) .join-item {
- border-start-end-radius: 0;
- border-end-end-radius: 0;
- border-end-start-radius: 0;
- border-start-start-radius: 0;
-}
-
-.join .join-item:first-child:not(:last-child),
- .join *:first-child:not(:last-child) .join-item {
- border-start-end-radius: 0;
- border-end-end-radius: 0;
-}
-
-.join .dropdown .join-item:first-child:not(:last-child),
- .join *:first-child:not(:last-child) .dropdown .join-item {
- border-start-end-radius: inherit;
- border-end-end-radius: inherit;
-}
-
-.join :where(.join-item:first-child:not(:last-child)),
- .join :where(*:first-child:not(:last-child) .join-item) {
- border-end-start-radius: inherit;
- border-start-start-radius: inherit;
-}
-
-.join .join-item:last-child:not(:first-child),
- .join *:last-child:not(:first-child) .join-item {
- border-end-start-radius: 0;
- border-start-start-radius: 0;
-}
-
-.join :where(.join-item:last-child:not(:first-child)),
- .join :where(*:last-child:not(:first-child) .join-item) {
- border-start-end-radius: inherit;
- border-end-end-radius: inherit;
-}
-
-@supports not selector(:has(*)) {
- :where(.join *) {
- border-radius: inherit;
- }
-}
-
-@supports selector(:has(*)) {
- :where(.join *:has(.join-item)) {
- border-radius: inherit;
- }
-}
-
-.link {
- cursor: pointer;
- text-decoration-line: underline;
-}
-
-.menu {
- display: flex;
- flex-direction: column;
- flex-wrap: wrap;
- font-size: 0.875rem;
- line-height: 1.25rem;
- padding: 0.5rem;
-}
-
-.menu :where(li ul) {
- position: relative;
- white-space: nowrap;
- margin-inline-start: 1rem;
- padding-inline-start: 0.5rem;
-}
-
-.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
- display: grid;
- grid-auto-flow: column;
- align-content: flex-start;
- align-items: center;
- gap: 0.5rem;
- grid-auto-columns: minmax(auto, max-content) auto max-content;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
-}
-
-.menu li.disabled {
- cursor: not-allowed;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- color: var(--fallback-bc,oklch(var(--bc)/0.3));
-}
-
-.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) {
- display: none;
-}
-
-:where(.menu li) {
- position: relative;
- display: flex;
- flex-shrink: 0;
- flex-direction: column;
- flex-wrap: wrap;
- align-items: stretch;
-}
-
-:where(.menu li) .badge {
- justify-self: end;
-}
-
-.modal {
- pointer-events: none;
- position: fixed;
- inset: 0px;
- margin: 0px;
- display: grid;
- height: 100%;
- max-height: none;
- width: 100%;
- max-width: none;
- justify-items: center;
- padding: 0px;
- opacity: 0;
- overscroll-behavior: contain;
- z-index: 999;
- background-color: transparent;
- color: inherit;
- transition-duration: 200ms;
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-property: transform, opacity, visibility;
- overflow-y: hidden;
-}
-
-:where(.modal) {
- align-items: center;
-}
-
-.modal-box {
- max-height: calc(100vh - 5em);
- grid-column-start: 1;
- grid-row-start: 1;
- width: 91.666667%;
- max-width: 32rem;
- --tw-scale-x: .9;
- --tw-scale-y: .9;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-bottom-right-radius: var(--rounded-box, 1rem);
- border-bottom-left-radius: var(--rounded-box, 1rem);
- border-top-left-radius: var(--rounded-box, 1rem);
- border-top-right-radius: var(--rounded-box, 1rem);
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
- padding: 1.5rem;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 200ms;
- box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px;
- overflow-y: auto;
- overscroll-behavior: contain;
-}
-
-.modal-open,
-.modal:target,
-.modal-toggle:checked + .modal,
-.modal[open] {
- pointer-events: auto;
- visibility: visible;
- opacity: 1;
-}
-
-:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) {
- overflow: hidden;
- scrollbar-gutter: stable;
-}
-
-.navbar {
- display: flex;
- align-items: center;
- padding: var(--navbar-padding, 0.5rem);
- min-height: 4rem;
- width: 100%;
-}
-
-:where(.navbar > *:not(script, style)) {
- display: inline-flex;
- align-items: center;
-}
-
-.navbar-end {
- width: 50%;
- justify-content: flex-end;
-}
-
-.progress {
- position: relative;
- width: 100%;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- overflow: hidden;
- height: 0.5rem;
- border-radius: var(--rounded-box, 1rem);
- background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.radio {
- flex-shrink: 0;
- --chkbg: var(--bc);
- height: 1.5rem;
- width: 1.5rem;
- cursor: pointer;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- border-radius: 9999px;
- border-width: 1px;
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- --tw-border-opacity: 0.2;
-}
-
-.range {
- height: 1.5rem;
- width: 100%;
- cursor: pointer;
- -moz-appearance: none;
- appearance: none;
- -webkit-appearance: none;
- --range-shdw: var(--fallback-bc,oklch(var(--bc)/1));
- overflow: hidden;
- border-radius: var(--rounded-box, 1rem);
- background-color: transparent;
-}
-
-.range:focus {
- outline: none;
-}
-
-.select {
- display: inline-flex;
- cursor: pointer;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- height: 3rem;
- min-height: 3rem;
- padding-inline-start: 1rem;
- padding-inline-end: 2.5rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
- line-height: 2;
- border-radius: var(--rounded-btn, 0.5rem);
- border-width: 1px;
- border-color: transparent;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
- background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
- linear-gradient(135deg, currentColor 50%, transparent 50%);
- background-position: calc(100% - 20px) calc(1px + 50%),
- calc(100% - 16.1px) calc(1px + 50%);
- background-size: 4px 4px,
- 4px 4px;
- background-repeat: no-repeat;
-}
-
-.select[multiple] {
- height: auto;
-}
-
-.steps {
- display: inline-grid;
- grid-auto-flow: column;
- overflow: hidden;
- overflow-x: auto;
- counter-reset: step;
- grid-auto-columns: 1fr;
-}
-
-.steps .step {
- display: grid;
- grid-template-columns: repeat(1, minmax(0, 1fr));
- grid-template-columns: auto;
- grid-template-rows: repeat(2, minmax(0, 1fr));
- grid-template-rows: 40px 1fr;
- place-items: center;
- text-align: center;
- min-width: 4rem;
-}
-
-.swap {
- position: relative;
- display: inline-grid;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- place-content: center;
- cursor: pointer;
-}
-
-.swap > * {
- grid-column-start: 1;
- grid-row-start: 1;
- transition-duration: 300ms;
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-property: transform, opacity;
-}
-
-.swap input {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-}
-
-.swap .swap-on,
-.swap .swap-indeterminate,
-.swap input:indeterminate ~ .swap-on {
- opacity: 0;
-}
-
-.swap input:checked ~ .swap-off,
-.swap-active .swap-off,
-.swap input:indeterminate ~ .swap-off {
- opacity: 0;
-}
-
-.swap input:checked ~ .swap-on,
-.swap-active .swap-on,
-.swap input:indeterminate ~ .swap-indeterminate {
- opacity: 1;
-}
-
-.tabs {
- display: grid;
- align-items: flex-end;
-}
-
-.tabs-lifted:has(.tab-content[class^="rounded-"])
- .tab:first-child:not(:is(.tab-active, [aria-selected="true"])), .tabs-lifted:has(.tab-content[class*=" rounded-"])
- .tab:first-child:not(:is(.tab-active, [aria-selected="true"])) {
- border-bottom-color: transparent;
-}
-
-.tab {
- position: relative;
- grid-row-start: 1;
- display: inline-flex;
- height: 2rem;
- cursor: pointer;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- flex-wrap: wrap;
- align-items: center;
- justify-content: center;
- text-align: center;
- font-size: 0.875rem;
- line-height: 1.25rem;
- line-height: 2;
- --tab-padding: 1rem;
- --tw-text-opacity: 0.5;
- --tab-color: var(--fallback-bc,oklch(var(--bc)/1));
- --tab-bg: var(--fallback-b1,oklch(var(--b1)/1));
- --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1));
- color: var(--tab-color);
- padding-inline-start: var(--tab-padding, 1rem);
- padding-inline-end: var(--tab-padding, 1rem);
-}
-
-.tab:is(input[type="radio"]) {
- width: auto;
- border-bottom-right-radius: 0px;
- border-bottom-left-radius: 0px;
-}
-
-.tab:is(input[type="radio"]):after {
- --tw-content: attr(aria-label);
- content: var(--tw-content);
-}
-
-.tab:not(input):empty {
- cursor: default;
- grid-column-start: span 9999;
-}
-
-.tab-content {
- grid-column-start: 1;
- grid-column-end: span 9999;
- grid-row-start: 2;
- margin-top: calc(var(--tab-border) * -1);
- display: none;
- border-color: transparent;
- border-width: var(--tab-border, 0);
-}
-
-:checked + .tab-content:nth-child(2),
- :is(.tab-active, [aria-selected="true"]) + .tab-content:nth-child(2) {
- border-start-start-radius: 0px;
-}
-
-input.tab:checked + .tab-content,
-:is(.tab-active, [aria-selected="true"]) + .tab-content {
- display: block;
-}
-
-.table {
- position: relative;
- width: 100%;
- border-radius: var(--rounded-box, 1rem);
- text-align: left;
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.table :where(.table-pin-rows thead tr) {
- position: sticky;
- top: 0px;
- z-index: 1;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
-
-.table :where(.table-pin-rows tfoot tr) {
- position: sticky;
- bottom: 0px;
- z-index: 1;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
-
-.table :where(.table-pin-cols tr th) {
- position: sticky;
- left: 0px;
- right: 0px;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
-
-.table-zebra tbody tr:nth-child(even) :where(.table-pin-cols tr th) {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
-}
-
-.textarea {
- min-height: 3rem;
- flex-shrink: 1;
- padding-left: 1rem;
- padding-right: 1rem;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
- line-height: 2;
- border-radius: var(--rounded-btn, 0.5rem);
- border-width: 1px;
- border-color: transparent;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
-
-.toggle {
- flex-shrink: 0;
- --tglbg: var(--fallback-b1,oklch(var(--b1)/1));
- --handleoffset: 1.5rem;
- --handleoffsetcalculator: calc(var(--handleoffset) * -1);
- --togglehandleborder: 0 0;
- height: 1.5rem;
- width: 3rem;
- cursor: pointer;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- border-radius: var(--rounded-badge, 1.9rem);
- border-width: 1px;
- border-color: currentColor;
- background-color: currentColor;
- color: var(--fallback-bc,oklch(var(--bc)/0.5));
- transition: background,
- box-shadow var(--animation-input, 0.2s) ease-out;
- box-shadow: var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset,
- 0 0 0 2px var(--tglbg) inset,
- var(--togglehandleborder);
-}
-
-.alert-info {
- border-color: var(--fallback-in,oklch(var(--in)/0.2));
- --tw-text-opacity: 1;
- color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
- --alert-bg: var(--fallback-in,oklch(var(--in)/1));
- --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
-}
-
-.alert-success {
- border-color: var(--fallback-su,oklch(var(--su)/0.2));
- --tw-text-opacity: 1;
- color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
- --alert-bg: var(--fallback-su,oklch(var(--su)/1));
- --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
-}
-
-.alert-warning {
- border-color: var(--fallback-wa,oklch(var(--wa)/0.2));
- --tw-text-opacity: 1;
- color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
- --alert-bg: var(--fallback-wa,oklch(var(--wa)/1));
- --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
-}
-
-.alert-error {
- border-color: var(--fallback-er,oklch(var(--er)/0.2));
- --tw-text-opacity: 1;
- color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
- --alert-bg: var(--fallback-er,oklch(var(--er)/1));
- --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
-}
-
-.avatar-group :where(.avatar) {
- overflow: hidden;
- border-radius: 9999px;
- border-width: 4px;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity)));
-}
-
-.badge-neutral {
- --tw-border-opacity: 1;
- border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
-}
-
-.badge-primary {
- --tw-border-opacity: 1;
- border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
-}
-
-.badge-ghost {
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
-}
-
-.badge-outline.badge-neutral {
- --tw-text-opacity: 1;
- color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity)));
-}
-
-.badge-outline.badge-primary {
- --tw-text-opacity: 1;
- color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
-}
-
-.btm-nav > *.disabled,
- .btm-nav > *[disabled] {
- pointer-events: none;
- --tw-border-opacity: 0;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-bg-opacity: 0.1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-text-opacity: 0.2;
-}
-
-.btm-nav > * .label {
- font-size: 1rem;
- line-height: 1.5rem;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .btn {
- animation: button-pop var(--animation-btn, 0.25s) ease-out;
- }
-}
-
-.btn:active:hover,
- .btn:active:focus {
- animation: button-pop 0s ease-out;
- transform: scale(var(--btn-focus-scale, 0.97));
-}
-
-@supports not (color: oklch(0% 0 0)) {
- .btn {
- background-color: var(--btn-color, var(--fallback-b2));
- border-color: var(--btn-color, var(--fallback-b2));
- }
-
- .btn-primary {
- --btn-color: var(--fallback-p);
- }
-
- .btn-neutral {
- --btn-color: var(--fallback-n);
- }
-}
-
-@supports (color: color-mix(in oklab, black, black)) {
- .btn-outline.btn-primary.btn-active {
- background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
- }
-
- .btn-outline.btn-secondary.btn-active {
- background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
- }
-
- .btn-outline.btn-accent.btn-active {
- background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
- }
-
- .btn-outline.btn-success.btn-active {
- background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
- }
-
- .btn-outline.btn-info.btn-active {
- background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
- }
-
- .btn-outline.btn-warning.btn-active {
- background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
- }
-
- .btn-outline.btn-error.btn-active {
- background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
- border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
- }
-}
-
-.btn:focus-visible {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
-}
-
-.btn-primary {
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
- outline-color: var(--fallback-p,oklch(var(--p)/1));
-}
-
-@supports (color: oklch(0% 0 0)) {
- .btn-primary {
- --btn-color: var(--p);
- }
-
- .btn-neutral {
- --btn-color: var(--n);
- }
-}
-
-.btn-neutral {
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
- outline-color: var(--fallback-n,oklch(var(--n)/1));
-}
-
-.btn.glass {
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
- outline-color: currentColor;
-}
-
-.btn.glass.btn-active {
- --glass-opacity: 25%;
- --glass-border-opacity: 15%;
-}
-
-.btn-ghost {
- border-width: 1px;
- border-color: transparent;
- background-color: transparent;
- color: currentColor;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
- outline-color: currentColor;
-}
-
-.btn-ghost.btn-active {
- border-color: transparent;
- background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.btn-outline {
- border-color: currentColor;
- background-color: transparent;
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
-}
-
-.btn-outline.btn-active {
- --tw-border-opacity: 1;
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-primary {
- --tw-text-opacity: 1;
- color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-primary.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-secondary {
- --tw-text-opacity: 1;
- color: var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-secondary.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-accent {
- --tw-text-opacity: 1;
- color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-accent.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-success {
- --tw-text-opacity: 1;
- color: var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-success.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-info {
- --tw-text-opacity: 1;
- color: var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-info.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-warning {
- --tw-text-opacity: 1;
- color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-warning.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-error {
- --tw-text-opacity: 1;
- color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)));
-}
-
-.btn-outline.btn-error.btn-active {
- --tw-text-opacity: 1;
- color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
-}
-
-.btn.btn-disabled,
- .btn[disabled],
- .btn:disabled {
- --tw-border-opacity: 0;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-bg-opacity: 0.2;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-text-opacity: 0.2;
-}
-
-.btn:is(input[type="checkbox"]:checked),
-.btn:is(input[type="radio"]:checked) {
- --tw-border-opacity: 1;
- border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
-}
-
-.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible {
- outline-color: var(--fallback-p,oklch(var(--p)/1));
-}
-
-@keyframes button-pop {
- 0% {
- transform: scale(var(--btn-focus-scale, 0.98));
- }
-
- 40% {
- transform: scale(1.02);
- }
-
- 100% {
- transform: scale(1);
- }
-}
-
-.card :where(figure:first-child) {
- overflow: hidden;
- border-start-start-radius: inherit;
- border-start-end-radius: inherit;
- border-end-start-radius: unset;
- border-end-end-radius: unset;
-}
-
-.card :where(figure:last-child) {
- overflow: hidden;
- border-start-start-radius: unset;
- border-start-end-radius: unset;
- border-end-start-radius: inherit;
- border-end-end-radius: inherit;
-}
-
-.card:focus-visible {
- outline: 2px solid currentColor;
- outline-offset: 2px;
-}
-
-.card.bordered {
- border-width: 1px;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
-}
-
-.card.compact .card-body {
- padding: 1rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.card-title {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 1.25rem;
- line-height: 1.75rem;
- font-weight: 600;
-}
-
-.card.image-full :where(figure) {
- overflow: hidden;
- border-radius: inherit;
-}
-
-.carousel::-webkit-scrollbar {
- display: none;
-}
-
-.checkbox:focus {
- box-shadow: none;
-}
-
-.checkbox:focus-visible {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/1));
-}
-
-.checkbox:disabled {
- border-width: 0px;
- cursor: not-allowed;
- border-color: transparent;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- opacity: 0.2;
-}
-
-.checkbox:checked,
- .checkbox[aria-checked="true"] {
- background-repeat: no-repeat;
- animation: checkmark var(--animation-input, 0.2s) ease-out;
- background-color: var(--chkbg);
- background-image: linear-gradient(-45deg, transparent 65%, var(--chkbg) 65.99%),
- linear-gradient(45deg, transparent 75%, var(--chkbg) 75.99%),
- linear-gradient(-45deg, var(--chkbg) 40%, transparent 40.99%),
- linear-gradient(
- 45deg,
- var(--chkbg) 30%,
- var(--chkfg) 30.99%,
- var(--chkfg) 40%,
- transparent 40.99%
- ),
- linear-gradient(-45deg, var(--chkfg) 50%, var(--chkbg) 50.99%);
-}
-
-.checkbox:indeterminate {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- background-repeat: no-repeat;
- animation: checkmark var(--animation-input, 0.2s) ease-out;
- background-image: linear-gradient(90deg, transparent 80%, var(--chkbg) 80%),
- linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%),
- linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%);
-}
-
-@keyframes checkmark {
- 0% {
- background-position-y: 5px;
- }
-
- 50% {
- background-position-y: -2px;
- }
-
- 100% {
- background-position-y: 0;
- }
-}
-
-details.collapse {
- width: 100%;
-}
-
-details.collapse summary {
- position: relative;
- display: block;
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-details.collapse summary::-webkit-details-marker {
- display: none;
-}
-
-.collapse:focus-visible {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/1));
-}
-
-.collapse:has(.collapse-title:focus-visible),
-.collapse:has(> input[type="checkbox"]:focus-visible),
-.collapse:has(> input[type="radio"]:focus-visible) {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/1));
-}
-
-.collapse-arrow > .collapse-title:after {
- position: absolute;
- display: block;
- height: 0.5rem;
- width: 0.5rem;
- --tw-translate-y: -100%;
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 150ms;
- transition-duration: 0.2s;
- top: 1.9rem;
- inset-inline-end: 1.4rem;
- content: "";
- transform-origin: 75% 75%;
- box-shadow: 2px 2px;
- pointer-events: none;
-}
-
-.collapse-plus > .collapse-title:after {
- position: absolute;
- display: block;
- height: 0.5rem;
- width: 0.5rem;
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 300ms;
- top: 0.9rem;
- inset-inline-end: 1.4rem;
- content: "+";
- pointer-events: none;
-}
-
-.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"],
-.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked),
-.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title {
- cursor: pointer;
-}
-
-.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title {
- cursor: unset;
-}
-
-.collapse-title {
- position: relative;
-}
-
-:where(.collapse > input[type="checkbox"]),
-:where(.collapse > input[type="radio"]) {
- z-index: 1;
-}
-
-.collapse-title,
-:where(.collapse > input[type="checkbox"]),
-:where(.collapse > input[type="radio"]) {
- width: 100%;
- padding: 1rem;
- padding-inline-end: 3rem;
- min-height: 3.75rem;
- transition: background-color 0.2s ease-out;
-}
-
-.collapse[open] > :where(.collapse-content),
-.collapse-open > :where(.collapse-content),
-.collapse:focus:not(.collapse-close) > :where(.collapse-content),
-.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content),
-.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content) {
- padding-bottom: 1rem;
- transition: padding 0.2s ease-out,
- background-color 0.2s ease-out;
-}
-
-.collapse[open].collapse-arrow > .collapse-title:after,
-.collapse-open.collapse-arrow > .collapse-title:after,
-.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after,
-.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after,
-.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after {
- --tw-translate-y: -50%;
- --tw-rotate: 225deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.collapse[open].collapse-plus > .collapse-title:after,
-.collapse-open.collapse-plus > .collapse-title:after,
-.collapse-plus:focus:not(.collapse-close) > .collapse-title:after,
-.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after,
-.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after {
- content: "−";
-}
-
-.divider:not(:empty) {
- gap: 1rem;
-}
-
-.drawer-toggle:checked ~ .drawer-side > .drawer-overlay {
- background-color: #0006;
-}
-
-.drawer-toggle:focus-visible ~ .drawer-content label.drawer-button {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
-}
-
-.dropdown.dropdown-open .dropdown-content,
-.dropdown:focus .dropdown-content,
-.dropdown:focus-within .dropdown-content {
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.label-text {
- font-size: 0.875rem;
- line-height: 1.25rem;
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
-}
-
-.input input {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
- background-color: transparent;
-}
-
-.input input:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.input[list]::-webkit-calendar-picker-indicator {
- line-height: 1em;
-}
-
-.input-bordered {
- border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.input:focus,
- .input:focus-within {
- box-shadow: none;
- border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.input-primary {
- --tw-border-opacity: 1;
- border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
-}
-
-.input-primary:focus,
- .input-primary:focus-within {
- --tw-border-opacity: 1;
- border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
- outline-color: var(--fallback-p,oklch(var(--p)/1));
-}
-
-.input:has(> input[disabled]),
- .input-disabled,
- .input:disabled,
- .input[disabled] {
- cursor: not-allowed;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
- color: var(--fallback-bc,oklch(var(--bc)/0.4));
-}
-
-.input:has(> input[disabled])::-moz-placeholder, .input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder {
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
- --tw-placeholder-opacity: 0.2;
-}
-
-.input:has(> input[disabled])::placeholder,
- .input-disabled::placeholder,
- .input:disabled::placeholder,
- .input[disabled]::placeholder {
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
- --tw-placeholder-opacity: 0.2;
-}
-
-.input:has(> input[disabled]) > input[disabled] {
- cursor: not-allowed;
-}
-
-.input::-webkit-date-and-time-value {
- text-align: inherit;
-}
-
-.join > :where(*:not(:first-child)) {
- margin-top: 0px;
- margin-bottom: 0px;
- margin-inline-start: -1px;
-}
-
-.join > :where(*:not(:first-child)):is(.btn) {
- margin-inline-start: calc(var(--border-btn) * -1);
-}
-
-.link-primary {
- --tw-text-opacity: 1;
- color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
-}
-
-@supports (color:color-mix(in oklab,black,black)) {
- @media (hover:hover) {
- .link-primary:hover {
- color: color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 80%,black);
- }
- }
-}
-
-.link:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.link:focus-visible {
- outline: 2px solid currentColor;
- outline-offset: 2px;
-}
-
-.loading {
- pointer-events: none;
- display: inline-block;
- aspect-ratio: 1 / 1;
- width: 1.5rem;
- background-color: currentColor;
- -webkit-mask-size: 100%;
- mask-size: 100%;
- -webkit-mask-repeat: no-repeat;
- mask-repeat: no-repeat;
- -webkit-mask-position: center;
- mask-position: center;
- -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
- mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
-}
-
-.loading-spinner {
- -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
- mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
-}
-
-.loading-ball {
- -webkit-mask-image: url("data:image/svg+xml,%0A%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_rXNP%7Banimation:spinner_YeBj .8s infinite%7D@keyframes spinner_YeBj%7B0%25%7Banimation-timing-function:cubic-bezier(0.33,0,.66,.33);cy:5px%7D46.875%25%7Bcy:20px;rx:4px;ry:4px%7D50%25%7Banimation-timing-function:cubic-bezier(0.33,.66,.66,1);cy:20.5px;rx:4.8px;ry:3px%7D53.125%25%7Brx:4px;ry:4px%7D100%25%7Bcy:5px%7D%7D%3C/style%3E%3Cellipse class='spinner_rXNP' cx='12' cy='5' rx='4' ry='4'/%3E%3C/svg%3E");
- mask-image: url("data:image/svg+xml,%0A%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_rXNP%7Banimation:spinner_YeBj .8s infinite%7D@keyframes spinner_YeBj%7B0%25%7Banimation-timing-function:cubic-bezier(0.33,0,.66,.33);cy:5px%7D46.875%25%7Bcy:20px;rx:4px;ry:4px%7D50%25%7Banimation-timing-function:cubic-bezier(0.33,.66,.66,1);cy:20.5px;rx:4.8px;ry:3px%7D53.125%25%7Brx:4px;ry:4px%7D100%25%7Bcy:5px%7D%7D%3C/style%3E%3Cellipse class='spinner_rXNP' cx='12' cy='5' rx='4' ry='4'/%3E%3C/svg%3E");
-}
-
-.loading-sm {
- width: 1.25rem;
-}
-
-.loading-md {
- width: 1.5rem;
-}
-
-.loading-lg {
- width: 2.5rem;
-}
-
-:where(.menu li:empty) {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- opacity: 0.1;
- margin: 0.5rem 1rem;
- height: 1px;
-}
-
-.menu :where(li ul):before {
- position: absolute;
- bottom: 0.75rem;
- inset-inline-start: 0px;
- top: 0.75rem;
- width: 1px;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- opacity: 0.1;
- content: "";
-}
-
-.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)),
-.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
- border-radius: var(--rounded-btn, 0.5rem);
- padding-left: 1rem;
- padding-right: 1rem;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
- text-align: start;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transition-duration: 200ms;
- text-wrap: balance;
-}
-
-:where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):is(summary):not(.active, .btn):focus-visible, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):is(summary):not(.active, .btn):focus-visible {
- cursor: pointer;
- background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.menu li > *:not(ul, .menu-title, details, .btn):active,
-.menu li > *:not(ul, .menu-title, details, .btn).active,
-.menu li > details > summary:active {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
-}
-
-.menu :where(li > details > summary)::-webkit-details-marker {
- display: none;
-}
-
-.menu :where(li > details > summary):after,
-.menu :where(li > .menu-dropdown-toggle):after {
- justify-self: end;
- display: block;
- margin-top: -0.5rem;
- height: 0.5rem;
- width: 0.5rem;
- transform: rotate(45deg);
- transition-property: transform, margin-top;
- transition-duration: 0.3s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- content: "";
- transform-origin: 75% 75%;
- box-shadow: 2px 2px;
- pointer-events: none;
-}
-
-.menu :where(li > details[open] > summary):after,
-.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after {
- transform: rotate(225deg);
- margin-top: 0;
-}
-
-.mockup-phone .display {
- overflow: hidden;
- border-radius: 40px;
- margin-top: -25px;
-}
-
-.mockup-browser .mockup-browser-toolbar .input {
- position: relative;
- margin-left: auto;
- margin-right: auto;
- display: block;
- height: 1.75rem;
- width: 24rem;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
- padding-left: 2rem;
- direction: ltr;
-}
-
-.mockup-browser .mockup-browser-toolbar .input:before {
- content: "";
- position: absolute;
- left: 0.5rem;
- top: 50%;
- aspect-ratio: 1 / 1;
- height: 0.75rem;
- --tw-translate-y: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-radius: 9999px;
- border-width: 2px;
- border-color: currentColor;
- opacity: 0.6;
-}
-
-.mockup-browser .mockup-browser-toolbar .input:after {
- content: "";
- position: absolute;
- left: 1.25rem;
- top: 50%;
- height: 0.5rem;
- --tw-translate-y: 25%;
- --tw-rotate: -45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-radius: 9999px;
- border-width: 1px;
- border-color: currentColor;
- opacity: 0.6;
-}
-
-.modal:not(dialog:not(.modal-open)),
- .modal::backdrop {
- background-color: #0006;
- animation: modal-pop 0.2s ease-out;
-}
-
-.modal-backdrop {
- z-index: -1;
- grid-column-start: 1;
- grid-row-start: 1;
- display: grid;
- align-self: stretch;
- justify-self: stretch;
- color: transparent;
-}
-
-.modal-open .modal-box,
-.modal-toggle:checked + .modal .modal-box,
-.modal:target .modal-box,
-.modal[open] .modal-box {
- --tw-translate-y: 0px;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-@keyframes modal-pop {
- 0% {
- opacity: 0;
- }
-}
-
-.progress::-moz-progress-bar {
- border-radius: var(--rounded-box, 1rem);
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
-}
-
-.progress:indeterminate {
- --progress-color: var(--fallback-bc,oklch(var(--bc)/1));
- background-image: repeating-linear-gradient(
- 90deg,
- var(--progress-color) -1%,
- var(--progress-color) 10%,
- transparent 10%,
- transparent 90%
- );
- background-size: 200%;
- background-position-x: 15%;
- animation: progress-loading 5s ease-in-out infinite;
-}
-
-.progress::-webkit-progress-bar {
- border-radius: var(--rounded-box, 1rem);
- background-color: transparent;
-}
-
-.progress::-webkit-progress-value {
- border-radius: var(--rounded-box, 1rem);
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
-}
-
-.progress:indeterminate::-moz-progress-bar {
- background-color: transparent;
- background-image: repeating-linear-gradient(
- 90deg,
- var(--progress-color) -1%,
- var(--progress-color) 10%,
- transparent 10%,
- transparent 90%
- );
- background-size: 200%;
- background-position-x: 15%;
- animation: progress-loading 5s ease-in-out infinite;
-}
-
-@keyframes progress-loading {
- 50% {
- background-position-x: -115%;
- }
-}
-
-.radio:focus {
- box-shadow: none;
-}
-
-.radio:focus-visible {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/1));
-}
-
-.radio:checked,
- .radio[aria-checked="true"] {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
- background-image: none;
- animation: radiomark var(--animation-input, 0.2s) ease-out;
- box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,
- 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset;
-}
-
-.radio:disabled {
- cursor: not-allowed;
- opacity: 0.2;
-}
-
-@keyframes radiomark {
- 0% {
- box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset,
- 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset;
- }
-
- 50% {
- box-shadow: 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset,
- 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset;
- }
-
- 100% {
- box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,
- 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset;
- }
-}
-
-.range:focus-visible::-webkit-slider-thumb {
- --focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset;
-}
-
-.range:focus-visible::-moz-range-thumb {
- --focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset;
-}
-
-.range::-webkit-slider-runnable-track {
- height: 0.5rem;
- width: 100%;
- border-radius: var(--rounded-box, 1rem);
- background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
-}
-
-.range::-moz-range-track {
- height: 0.5rem;
- width: 100%;
- border-radius: var(--rounded-box, 1rem);
- background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
-}
-
-.range::-webkit-slider-thumb {
- position: relative;
- height: 1.5rem;
- width: 1.5rem;
- border-radius: var(--rounded-box, 1rem);
- border-style: none;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
- appearance: none;
- -webkit-appearance: none;
- top: 50%;
- color: var(--range-shdw);
- transform: translateY(-50%);
- --filler-size: 100rem;
- --filler-offset: 0.6rem;
- box-shadow: 0 0 0 3px var(--range-shdw) inset,
- var(--focus-shadow, 0 0),
- calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size);
-}
-
-.range::-moz-range-thumb {
- position: relative;
- height: 1.5rem;
- width: 1.5rem;
- border-radius: var(--rounded-box, 1rem);
- border-style: none;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
- top: 50%;
- color: var(--range-shdw);
- --filler-size: 100rem;
- --filler-offset: 0.5rem;
- box-shadow: 0 0 0 3px var(--range-shdw) inset,
- var(--focus-shadow, 0 0),
- calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size);
-}
-
-@keyframes rating-pop {
- 0% {
- transform: translateY(-0.125em);
- }
-
- 40% {
- transform: translateY(-0.125em);
- }
-
- 100% {
- transform: translateY(0);
- }
-}
-
-.select-bordered {
- border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.select:focus {
- box-shadow: none;
- border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.select-disabled,
- .select:disabled,
- .select[disabled] {
- cursor: not-allowed;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
- color: var(--fallback-bc,oklch(var(--bc)/0.4));
-}
-
-.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder {
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
- --tw-placeholder-opacity: 0.2;
-}
-
-.select-disabled::placeholder,
- .select:disabled::placeholder,
- .select[disabled]::placeholder {
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
- --tw-placeholder-opacity: 0.2;
-}
-
-.select-multiple,
- .select[multiple],
- .select[size].select:not([size="1"]) {
- background-image: none;
- padding-right: 1rem;
-}
-
-[dir="rtl"] .select {
- background-position: calc(0% + 12px) calc(1px + 50%),
- calc(0% + 16px) calc(1px + 50%);
-}
-
-.skeleton {
- border-radius: var(--rounded-box, 1rem);
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
- will-change: background-position;
- animation: skeleton 1.8s ease-in-out infinite;
- background-image: linear-gradient(
- 105deg,
- transparent 0%,
- transparent 40%,
- var(--fallback-b1,oklch(var(--b1)/1)) 50%,
- transparent 60%,
- transparent 100%
- );
- background-size: 200% auto;
- background-repeat: no-repeat;
- background-position-x: -50%;
-}
-
-@media (prefers-reduced-motion) {
- .skeleton {
- animation-duration: 15s;
- }
-}
-
-@keyframes skeleton {
- from {
- background-position: 150%;
- }
-
- to {
- background-position: -50%;
- }
-}
-
-.steps .step:before {
- top: 0px;
- grid-column-start: 1;
- grid-row-start: 1;
- height: 0.5rem;
- width: 100%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- content: "";
- margin-inline-start: -100%;
-}
-
-.steps .step:after {
- content: counter(step);
- counter-increment: step;
- z-index: 1;
- position: relative;
- grid-column-start: 1;
- grid-row-start: 1;
- display: grid;
- height: 2rem;
- width: 2rem;
- place-items: center;
- place-self: center;
- border-radius: 9999px;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
-}
-
-.steps .step:first-child:before {
- content: none;
-}
-
-.steps .step[data-content]:after {
- content: attr(data-content);
-}
-
-.steps .step-neutral + .step-neutral:before,
- .steps .step-neutral:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
-}
-
-.steps .step-primary + .step-primary:before,
- .steps .step-primary:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
-}
-
-.steps .step-secondary + .step-secondary:before,
- .steps .step-secondary:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
-}
-
-.steps .step-accent + .step-accent:before,
- .steps .step-accent:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
-}
-
-.steps .step-info + .step-info:before {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));
-}
-
-.steps .step-info:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
-}
-
-.steps .step-success + .step-success:before {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
-}
-
-.steps .step-success:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
-}
-
-.steps .step-warning + .step-warning:before {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));
-}
-
-.steps .step-warning:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
-}
-
-.steps .step-error + .step-error:before {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));
-}
-
-.steps .step-error:after {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
-}
-
-.swap-rotate .swap-on,
-.swap-rotate .swap-indeterminate,
-.swap-rotate input:indeterminate ~ .swap-on {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.swap-rotate input:checked ~ .swap-off,
-.swap-active:where(.swap-rotate) .swap-off,
-.swap-rotate input:indeterminate ~ .swap-off {
- --tw-rotate: -45deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.swap-rotate input:checked ~ .swap-on,
-.swap-active:where(.swap-rotate) .swap-on,
-.swap-rotate input:indeterminate ~ .swap-indeterminate {
- --tw-rotate: 0deg;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.swap-flip .swap-on,
-.swap-flip .swap-indeterminate,
-.swap-flip input:indeterminate ~ .swap-on {
- transform: rotateY(180deg);
- backface-visibility: hidden;
- opacity: 1;
-}
-
-.swap-flip input:checked ~ .swap-off,
-.swap-active:where(.swap-flip) .swap-off,
-.swap-flip input:indeterminate ~ .swap-off {
- transform: rotateY(-180deg);
- backface-visibility: hidden;
- opacity: 1;
-}
-
-.swap-flip input:checked ~ .swap-on,
-.swap-active:where(.swap-flip) .swap-on,
-.swap-flip input:indeterminate ~ .swap-indeterminate {
- transform: rotateY(0deg);
-}
-
-.tabs-lifted > .tab:focus-visible {
- border-end-end-radius: 0;
- border-end-start-radius: 0;
-}
-
-.tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tab:is(input:checked) {
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- --tw-border-opacity: 1;
- --tw-text-opacity: 1;
-}
-
-.tab:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.tab:focus-visible {
- outline: 2px solid currentColor;
- outline-offset: -5px;
-}
-
-.tab-disabled,
- .tab[disabled] {
- cursor: not-allowed;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- --tw-text-opacity: 0.2;
-}
-
-.tabs-bordered > .tab {
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- --tw-border-opacity: 0.2;
- border-style: solid;
- border-bottom-width: calc(var(--tab-border, 1px) + 1px);
-}
-
-.tabs-lifted > .tab {
- border: var(--tab-border, 1px) solid transparent;
- border-width: 0 0 var(--tab-border, 1px) 0;
- border-start-start-radius: var(--tab-radius, 0.5rem);
- border-start-end-radius: var(--tab-radius, 0.5rem);
- border-bottom-color: var(--tab-border-color);
- padding-inline-start: var(--tab-padding, 1rem);
- padding-inline-end: var(--tab-padding, 1rem);
- padding-top: var(--tab-border, 1px);
-}
-
-.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tabs-lifted > .tab:is(input:checked) {
- background-color: var(--tab-bg);
- border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px);
- border-inline-start-color: var(--tab-border-color);
- border-inline-end-color: var(--tab-border-color);
- border-top-color: var(--tab-border-color);
- padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px));
- padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px));
- padding-bottom: var(--tab-border, 1px);
- padding-top: 0;
-}
-
-.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked):before {
- z-index: 1;
- content: "";
- display: block;
- position: absolute;
- width: calc(100% + var(--tab-radius, 0.5rem) * 2);
- height: var(--tab-radius, 0.5rem);
- bottom: 0;
- background-size: var(--tab-radius, 0.5rem);
- background-position: top left,
- top right;
- background-repeat: no-repeat;
- --tab-grad: calc(69% - var(--tab-border, 1px));
- --radius-start: radial-gradient(
- circle at top left,
- transparent var(--tab-grad),
- var(--tab-border-color) calc(var(--tab-grad) + 0.25px),
- var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)),
- var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px)
- );
- --radius-end: radial-gradient(
- circle at top right,
- transparent var(--tab-grad),
- var(--tab-border-color) calc(var(--tab-grad) + 0.25px),
- var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)),
- var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px)
- );
- background-image: var(--radius-start), var(--radius-end);
-}
-
-.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted > .tab:is(input:checked):first-child:before {
- background-image: var(--radius-end);
- background-position: top right;
-}
-
-[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):first-child:before {
- background-image: var(--radius-start);
- background-position: top left;
-}
-
-.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted > .tab:is(input:checked):last-child:before {
- background-image: var(--radius-start);
- background-position: top left;
-}
-
-[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):last-child:before {
- background-image: var(--radius-end);
- background-position: top right;
-}
-
-.tabs-lifted
- > :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled])
- + .tabs-lifted
- :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before {
- background-image: var(--radius-end);
- background-position: top right;
-}
-
-.tabs-boxed .tab {
- border-radius: var(--rounded-btn, 0.5rem);
-}
-
-.table:where([dir="rtl"], [dir="rtl"] *) {
- text-align: right;
-}
-
-.table :where(th, td) {
- padding-left: 1rem;
- padding-right: 1rem;
- padding-top: 0.75rem;
- padding-bottom: 0.75rem;
- vertical-align: middle;
-}
-
-.table tr.active,
- .table tr.active:nth-child(even),
- .table-zebra tbody tr:nth-child(even) {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
-}
-
-.table-zebra tr.active,
- .table-zebra tr.active:nth-child(even),
- .table-zebra-zebra tbody tr:nth-child(even) {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
-}
-
-.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) {
- border-bottom-width: 1px;
- --tw-border-opacity: 1;
- border-bottom-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
-}
-
-.table :where(thead, tfoot) {
- white-space: nowrap;
- font-size: 0.75rem;
- line-height: 1rem;
- font-weight: 700;
- color: var(--fallback-bc,oklch(var(--bc)/0.6));
-}
-
-.table :where(tfoot) {
- border-top-width: 1px;
- --tw-border-opacity: 1;
- border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
-}
-
-.textarea-bordered {
- border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.textarea:focus {
- box-shadow: none;
- border-color: var(--fallback-bc,oklch(var(--bc)/0.2));
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.textarea-disabled,
- .textarea:disabled,
- .textarea[disabled] {
- cursor: not-allowed;
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
- color: var(--fallback-bc,oklch(var(--bc)/0.4));
-}
-
-.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder {
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
- --tw-placeholder-opacity: 0.2;
-}
-
-.textarea-disabled::placeholder,
- .textarea:disabled::placeholder,
- .textarea[disabled]::placeholder {
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
- --tw-placeholder-opacity: 0.2;
-}
-
-@keyframes toast-pop {
- 0% {
- transform: scale(0.9);
- opacity: 0;
- }
-
- 100% {
- transform: scale(1);
- opacity: 1;
- }
-}
-
-[dir="rtl"] .toggle {
- --handleoffsetcalculator: calc(var(--handleoffset) * 1);
-}
-
-.toggle:focus-visible {
- outline-style: solid;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
-}
-
-.toggle:hover {
- background-color: currentColor;
-}
-
-.toggle:checked,
- .toggle[aria-checked="true"] {
- background-image: none;
- --handleoffsetcalculator: var(--handleoffset);
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
-}
-
-[dir="rtl"] .toggle:checked, [dir="rtl"] .toggle[aria-checked="true"] {
- --handleoffsetcalculator: calc(var(--handleoffset) * -1);
-}
-
-.toggle:indeterminate {
- --tw-text-opacity: 1;
- color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
- box-shadow: calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,
- calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,
- 0 0 0 2px var(--tglbg) inset;
-}
-
-[dir="rtl"] .toggle:indeterminate {
- box-shadow: calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,
- calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,
- 0 0 0 2px var(--tglbg) inset;
-}
-
-.toggle-primary:focus-visible {
- outline-color: var(--fallback-p,oklch(var(--p)/1));
-}
-
-.toggle-primary:checked,
- .toggle-primary[aria-checked="true"] {
- border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
- --tw-border-opacity: 0.1;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
-}
-
-.toggle-success:focus-visible {
- outline-color: var(--fallback-su,oklch(var(--su)/1));
-}
-
-.toggle-success:checked,
- .toggle-success[aria-checked="true"] {
- border-color: var(--fallback-su,oklch(var(--su)/var(--tw-border-opacity)));
- --tw-border-opacity: 0.1;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
- --tw-text-opacity: 1;
- color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
-}
-
-.toggle:disabled {
- cursor: not-allowed;
- --tw-border-opacity: 1;
- border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
- background-color: transparent;
- opacity: 0.3;
- --togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset,
- var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset;
-}
-
-.artboard.phone {
- width: 320px;
-}
-
-.badge-sm {
- height: 1rem;
- font-size: 0.75rem;
- line-height: 1rem;
- padding-left: 0.438rem;
- padding-right: 0.438rem;
-}
-
-.badge-lg {
- height: 1.5rem;
- font-size: 1rem;
- line-height: 1.5rem;
- padding-left: 0.688rem;
- padding-right: 0.688rem;
-}
-
-.btn-xs {
- height: 1.5rem;
- min-height: 1.5rem;
- padding-left: 0.5rem;
- padding-right: 0.5rem;
- font-size: 0.75rem;
-}
-
-.btn-sm {
- height: 2rem;
- min-height: 2rem;
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- font-size: 0.875rem;
-}
-
-.btn-block {
- width: 100%;
-}
-
-.btn-square:where(.btn-xs) {
- height: 1.5rem;
- width: 1.5rem;
- padding: 0px;
-}
-
-.btn-square:where(.btn-sm) {
- height: 2rem;
- width: 2rem;
- padding: 0px;
-}
-
-.btn-square:where(.btn-md) {
- height: 3rem;
- width: 3rem;
- padding: 0px;
-}
-
-.btn-square:where(.btn-lg) {
- height: 4rem;
- width: 4rem;
- padding: 0px;
-}
-
-.btn-circle:where(.btn-xs) {
- height: 1.5rem;
- width: 1.5rem;
- border-radius: 9999px;
- padding: 0px;
-}
-
-.btn-circle:where(.btn-sm) {
- height: 2rem;
- width: 2rem;
- border-radius: 9999px;
- padding: 0px;
-}
-
-.btn-circle:where(.btn-md) {
- height: 3rem;
- width: 3rem;
- border-radius: 9999px;
- padding: 0px;
-}
-
-.btn-circle:where(.btn-lg) {
- height: 4rem;
- width: 4rem;
- border-radius: 9999px;
- padding: 0px;
-}
-
-[type="checkbox"].checkbox-xs {
- height: 1rem;
- width: 1rem;
-}
-
-.divider-horizontal {
- flex-direction: column;
-}
-
-.divider-horizontal:before {
- height: 100%;
- width: 0.125rem;
-}
-
-.divider-horizontal:after {
- height: 100%;
- width: 0.125rem;
-}
-
-.drawer-open > .drawer-toggle {
- display: none;
-}
-
-.drawer-open > .drawer-toggle ~ .drawer-side {
- pointer-events: auto;
- visibility: visible;
- position: sticky;
- display: block;
- width: auto;
- overscroll-behavior: auto;
-}
-
-.drawer-open > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(0%);
-}
-
-[dir="rtl"] .drawer-open > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) {
- transform: translateX(0%);
-}
-
-.drawer-open > .drawer-toggle:checked ~ .drawer-side {
- pointer-events: auto;
- visibility: visible;
-}
-
-.drawer-open > .drawer-side {
- overflow-y: auto;
-}
-
-html:has(.drawer-toggle:checked) {
- overflow-y: hidden;
- scrollbar-gutter: stable;
-}
-
-.indicator :where(.indicator-item) {
- bottom: auto;
- inset-inline-end: 0px;
- inset-inline-start: auto;
- top: 0px;
- --tw-translate-y: -50%;
- --tw-translate-x: 50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item):where([dir="rtl"], [dir="rtl"] *) {
- --tw-translate-x: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-start) {
- inset-inline-end: auto;
- inset-inline-start: 0px;
- --tw-translate-x: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-start):where([dir="rtl"], [dir="rtl"] *) {
- --tw-translate-x: 50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-center) {
- inset-inline-end: 50%;
- inset-inline-start: 50%;
- --tw-translate-x: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-center):where([dir="rtl"], [dir="rtl"] *) {
- --tw-translate-x: 50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-end) {
- inset-inline-end: 0px;
- inset-inline-start: auto;
- --tw-translate-x: 50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-end):where([dir="rtl"], [dir="rtl"] *) {
- --tw-translate-x: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-bottom) {
- bottom: 0px;
- top: auto;
- --tw-translate-y: 50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-middle) {
- bottom: 50%;
- top: 50%;
- --tw-translate-y: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.indicator :where(.indicator-item.indicator-top) {
- bottom: auto;
- top: 0px;
- --tw-translate-y: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.join.join-vertical {
- flex-direction: column;
-}
-
-.join.join-vertical .join-item:first-child:not(:last-child),
- .join.join-vertical *:first-child:not(:last-child) .join-item {
- border-end-start-radius: 0;
- border-end-end-radius: 0;
- border-start-start-radius: inherit;
- border-start-end-radius: inherit;
-}
-
-.join.join-vertical .join-item:last-child:not(:first-child),
- .join.join-vertical *:last-child:not(:first-child) .join-item {
- border-start-start-radius: 0;
- border-start-end-radius: 0;
- border-end-start-radius: inherit;
- border-end-end-radius: inherit;
-}
-
-.join.join-horizontal {
- flex-direction: row;
-}
-
-.join.join-horizontal .join-item:first-child:not(:last-child),
- .join.join-horizontal *:first-child:not(:last-child) .join-item {
- border-end-end-radius: 0;
- border-start-end-radius: 0;
- border-end-start-radius: inherit;
- border-start-start-radius: inherit;
-}
-
-.join.join-horizontal .join-item:last-child:not(:first-child),
- .join.join-horizontal *:last-child:not(:first-child) .join-item {
- border-end-start-radius: 0;
- border-start-start-radius: 0;
- border-end-end-radius: inherit;
- border-start-end-radius: inherit;
-}
-
-.menu-horizontal {
- display: inline-flex;
- flex-direction: row;
-}
-
-.menu-horizontal > li:not(.menu-title) > details > ul {
- position: absolute;
-}
-
-.modal-bottom {
- place-items: end;
-}
-
-.steps-horizontal .step {
- display: grid;
- grid-template-columns: repeat(1, minmax(0, 1fr));
- grid-template-rows: repeat(2, minmax(0, 1fr));
- place-items: center;
- text-align: center;
-}
-
-.steps-vertical {
- grid-auto-rows: 1fr;
- grid-auto-flow: row;
-}
-
-.steps-vertical .step {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- grid-template-rows: repeat(1, minmax(0, 1fr));
-}
-
-.tabs-md :where(.tab) {
- height: 2rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
- line-height: 2;
- --tab-padding: 1rem;
-}
-
-.tabs-lg :where(.tab) {
- height: 3rem;
- font-size: 1.125rem;
- line-height: 1.75rem;
- line-height: 2;
- --tab-padding: 1.25rem;
-}
-
-.tabs-sm :where(.tab) {
- height: 1.5rem;
- font-size: 0.875rem;
- line-height: .75rem;
- --tab-padding: 0.75rem;
-}
-
-.tabs-xs :where(.tab) {
- height: 1.25rem;
- font-size: 0.75rem;
- line-height: .75rem;
- --tab-padding: 0.5rem;
-}
-
-[type="checkbox"].toggle-sm {
- --handleoffset: 0.75rem;
- height: 1.25rem;
- width: 2rem;
-}
-
-.avatar.online:before {
- content: "";
- position: absolute;
- z-index: 10;
- display: block;
- border-radius: 9999px;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
- outline-style: solid;
- outline-width: 2px;
- outline-color: var(--fallback-b1,oklch(var(--b1)/1));
- width: 15%;
- height: 15%;
- top: 7%;
- right: 7%;
-}
-
-.avatar.offline:before {
- content: "";
- position: absolute;
- z-index: 10;
- display: block;
- border-radius: 9999px;
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
- outline-style: solid;
- outline-width: 2px;
- outline-color: var(--fallback-b1,oklch(var(--b1)/1));
- width: 15%;
- height: 15%;
- top: 7%;
- right: 7%;
-}
-
-.card-compact .card-body {
- padding: 1rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.card-compact .card-title {
- margin-bottom: 0.25rem;
-}
-
-.card-normal .card-body {
- padding: var(--padding-card, 2rem);
- font-size: 1rem;
- line-height: 1.5rem;
-}
-
-.card-normal .card-title {
- margin-bottom: 0.75rem;
-}
-
-.divider-horizontal {
- margin-left: 1rem;
- margin-right: 1rem;
- margin-top: 0px;
- margin-bottom: 0px;
- height: auto;
- width: 1rem;
-}
-
-.drawer-open > .drawer-toggle ~ .drawer-side > .drawer-overlay {
- cursor: default;
- background-color: transparent;
-}
-
-.join.join-vertical > :where(*:not(:first-child)) {
- margin-left: 0px;
- margin-right: 0px;
- margin-top: -1px;
-}
-
-.join.join-vertical > :where(*:not(:first-child)):is(.btn) {
- margin-top: calc(var(--border-btn) * -1);
-}
-
-.join.join-horizontal > :where(*:not(:first-child)) {
- margin-top: 0px;
- margin-bottom: 0px;
- margin-inline-start: -1px;
-}
-
-.join.join-horizontal > :where(*:not(:first-child)):is(.btn) {
- margin-inline-start: calc(var(--border-btn) * -1);
-}
-
-.menu-horizontal > li:not(.menu-title) > details > ul {
- margin-inline-start: 0px;
- margin-top: 1rem;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
- padding-inline-end: 0.5rem;
-}
-
-.menu-horizontal > li > details > ul:before {
- content: none;
-}
-
-:where(.menu-horizontal > li:not(.menu-title) > details > ul) {
- border-radius: var(--rounded-box, 1rem);
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
- --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
-}
-
-.menu-sm :where(li:not(.menu-title) > *:not(ul, details, .menu-title)), .menu-sm :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
- border-radius: var(--rounded-btn, 0.5rem);
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- padding-top: 0.25rem;
- padding-bottom: 0.25rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
-}
-
-.menu-sm .menu-title {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-
-.modal-top :where(.modal-box) {
- width: 100%;
- max-width: none;
- --tw-translate-y: -2.5rem;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-bottom-right-radius: var(--rounded-box, 1rem);
- border-bottom-left-radius: var(--rounded-box, 1rem);
- border-top-left-radius: 0px;
- border-top-right-radius: 0px;
-}
-
-.modal-middle :where(.modal-box) {
- width: 91.666667%;
- max-width: 32rem;
- --tw-translate-y: 0px;
- --tw-scale-x: .9;
- --tw-scale-y: .9;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-top-left-radius: var(--rounded-box, 1rem);
- border-top-right-radius: var(--rounded-box, 1rem);
- border-bottom-right-radius: var(--rounded-box, 1rem);
- border-bottom-left-radius: var(--rounded-box, 1rem);
-}
-
-.modal-bottom :where(.modal-box) {
- width: 100%;
- max-width: none;
- --tw-translate-y: 2.5rem;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-top-left-radius: var(--rounded-box, 1rem);
- border-top-right-radius: var(--rounded-box, 1rem);
- border-bottom-right-radius: 0px;
- border-bottom-left-radius: 0px;
-}
-
-.steps-horizontal .step {
- grid-template-rows: 40px 1fr;
- grid-template-columns: auto;
- min-width: 4rem;
-}
-
-.steps-horizontal .step:before {
- height: 0.5rem;
- width: 100%;
- --tw-translate-x: 0px;
- --tw-translate-y: 0px;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- content: "";
- margin-inline-start: -100%;
-}
-
-.steps-horizontal .step:where([dir="rtl"], [dir="rtl"] *):before {
- --tw-translate-x: 0px;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.steps-vertical .step {
- gap: 0.5rem;
- grid-template-columns: 40px 1fr;
- grid-template-rows: auto;
- min-height: 4rem;
- justify-items: start;
-}
-
-.steps-vertical .step:before {
- height: 100%;
- width: 0.5rem;
- --tw-translate-x: -50%;
- --tw-translate-y: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- margin-inline-start: 50%;
-}
-
-.steps-vertical .step:where([dir="rtl"], [dir="rtl"] *):before {
- --tw-translate-x: 50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.alert {
- grid-auto-flow: column;
-}
-
-.container {
- max-width: 100%;
- width: 100%;
-}
-
-@media (min-width: 640px) {
- .container {
- width: 100%;
- }
-}
-
-@media (min-width: 768px) {
- .container {
- width: 100%;
- }
-}
-
-@media (min-width: 1024px) {
- .container {
- width: 960px;
- }
-}
-
-.main-bg {
- width: 100vw;
- position: fixed;
- left: 0;
- top: 40vw;
- opacity: 0.25;
- z-index: -1;
-}
-
-@media (min-width: 640px) {
- .main-bg {
- top: 50vw;
- }
-}
-
-@media (min-width: 768px) {
- .main-bg {
- top: 45vw;
- }
-}
-
-@media (min-width: 1024px) {
- .main-bg {
- top: 20vw;
- }
-}
-
-@media (min-width: 1280px) {
- .main-bg {
- top: 20vw;
- }
-}
-
-.main-bg.top {
- top: 0;
-}
-
-.header-hero {
- max-width: 70vw;
- margin: 0 auto;
-}
-
-@media (min-width: 640px) {
- .header-hero {
- max-width: 90vw;
- }
-}
-
-@media (min-width: 768px) {
- .header-hero {
- max-width: 90vw;
- }
-}
-
-@media (min-width: 1024px) {
- .header-hero {
- max-width: 70vw;
- }
-}
-
-@media (min-width: 1280px) {
- .header-hero {
- max-width: 70vw;
- }
-}
-
-.tab:is(input[type="radio"]) {
- border-bottom-right-radius: inherit;
- border-bottom-left-radius: inherit;
-}
-
-.carousel-control-left {
- --btn-focus-scale: 1;
- --animation-btn: 0;
- --animation-input: 0;
- position: absolute;
- left: 0px;
- top: 50%;
- margin: -0.25rem;
- margin-right: 2rem;
- --tw-translate-y: -50%;
- --tw-translate-x: -100%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.carousel-control-right {
- --btn-focus-scale: 1;
- --animation-btn: 0;
- --animation-input: 0;
- position: absolute;
- right: 0px;
- top: 50%;
- margin: -0.25rem;
- margin-left: 2rem;
- --tw-translate-y: -50%;
- --tw-translate-x: 100%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
-}
-
-.visible {
- visibility: visible;
-}
-
-.collapse {
- visibility: collapse;
-}
-
-.fixed {
- position: fixed;
-}
-
-.absolute {
- position: absolute;
-}
-
-.relative {
- position: relative;
-}
-
-.sticky {
- position: sticky;
-}
-
-.bottom-0 {
- bottom: 0px;
-}
-
-.left-0 {
- left: 0px;
-}
-
-.left-4 {
- left: 1rem;
-}
-
-.top-0 {
- top: 0px;
-}
-
-.top-1\/2 {
- top: 50%;
-}
-
-.top-12 {
- top: 3rem;
-}
-
-.z-10 {
- z-index: 10;
-}
-
-.z-40 {
- z-index: 40;
-}
-
-.z-50 {
- z-index: 50;
-}
-
-.z-\[10\] {
- z-index: 10;
-}
-
-.z-\[1\] {
- z-index: 1;
-}
-
-.z-\[9999\] {
- z-index: 9999;
-}
-
-.z-\[9\] {
- z-index: 9;
-}
-
-.col-span-2 {
- grid-column: span 2 / span 2;
-}
-
-.col-span-5 {
- grid-column: span 5 / span 5;
-}
-
-.float-end {
- float: inline-end;
-}
-
-.m-1 {
- margin: 0.25rem;
-}
-
-.mx-4 {
- margin-left: 1rem;
- margin-right: 1rem;
-}
-
-.mx-auto {
- margin-left: auto;
- margin-right: auto;
-}
-
-.my-1 {
- margin-top: 0.25rem;
- margin-bottom: 0.25rem;
-}
-
-.my-2 {
- margin-top: 0.5rem;
- margin-bottom: 0.5rem;
-}
-
-.my-3 {
- margin-top: 0.75rem;
- margin-bottom: 0.75rem;
-}
-
-.my-4 {
- margin-top: 1rem;
- margin-bottom: 1rem;
-}
-
-.my-8 {
- margin-top: 2rem;
- margin-bottom: 2rem;
-}
-
-.-mb-1 {
- margin-bottom: -0.25rem;
-}
-
-.mb-11 {
- margin-bottom: 2.75rem;
-}
-
-.mb-2 {
- margin-bottom: 0.5rem;
-}
-
-.mb-4 {
- margin-bottom: 1rem;
-}
-
-.mb-5 {
- margin-bottom: 1.25rem;
-}
-
-.mb-8 {
- margin-bottom: 2rem;
-}
-
-.ml-4 {
- margin-left: 1rem;
-}
-
-.mr-2 {
- margin-right: 0.5rem;
-}
-
-.mr-4 {
- margin-right: 1rem;
-}
-
-.mr-5 {
- margin-right: 1.25rem;
-}
-
-.mt-16 {
- margin-top: 4rem;
-}
-
-.mt-2 {
- margin-top: 0.5rem;
-}
-
-.mt-20 {
- margin-top: 5rem;
-}
-
-.mt-3 {
- margin-top: 0.75rem;
-}
-
-.mt-4 {
- margin-top: 1rem;
-}
-
-.mt-5 {
- margin-top: 1.25rem;
-}
-
-.mt-8 {
- margin-top: 2rem;
-}
-
-.block {
- display: block;
-}
-
-.inline-block {
- display: inline-block;
-}
-
-.inline {
- display: inline;
-}
-
-.flex {
- display: flex;
-}
-
-.inline-flex {
- display: inline-flex;
-}
-
-.table {
- display: table;
-}
-
-.grid {
- display: grid;
-}
-
-.contents {
- display: contents;
-}
-
-.hidden {
- display: none;
-}
-
-.aspect-\[4\/3\] {
- aspect-ratio: 4/3;
-}
-
-.aspect-video {
- aspect-ratio: 16 / 9;
-}
-
-.size-4 {
- width: 1rem;
- height: 1rem;
-}
-
-.size-5 {
- width: 1.25rem;
- height: 1.25rem;
-}
-
-.h-0 {
- height: 0px;
-}
-
-.h-10 {
- height: 2.5rem;
-}
-
-.h-11 {
- height: 2.75rem;
-}
-
-.h-24 {
- height: 6rem;
-}
-
-.h-3 {
- height: 0.75rem;
-}
-
-.h-4 {
- height: 1rem;
-}
-
-.h-6 {
- height: 1.5rem;
-}
-
-.h-8 {
- height: 2rem;
-}
-
-.h-96 {
- height: 24rem;
-}
-
-.h-\[3px\] {
- height: 3px;
-}
-
-.h-\[calc\(100vh\)\] {
- height: calc(100vh);
-}
-
-.h-auto {
- height: auto;
-}
-
-.h-screen {
- height: 100vh;
-}
-
-.max-h-44 {
- max-height: 11rem;
-}
-
-.min-h-fit {
- min-height: -moz-fit-content;
- min-height: fit-content;
-}
-
-.min-h-full {
- min-height: 100%;
-}
-
-.min-h-screen {
- min-height: 100vh;
-}
-
-.w-0 {
- width: 0px;
-}
-
-.w-1\/2 {
- width: 50%;
-}
-
-.w-10 {
- width: 2.5rem;
-}
-
-.w-10\/12 {
- width: 83.333333%;
-}
-
-.w-24 {
- width: 6rem;
-}
-
-.w-3 {
- width: 0.75rem;
-}
-
-.w-48 {
- width: 12rem;
-}
-
-.w-52 {
- width: 13rem;
-}
-
-.w-56 {
- width: 14rem;
-}
-
-.w-6 {
- width: 1.5rem;
-}
-
-.w-8 {
- width: 2rem;
-}
-
-.w-fit {
- width: -moz-fit-content;
- width: fit-content;
-}
-
-.w-full {
- width: 100%;
-}
-
-.max-w-\[8rem\] {
- max-width: 8rem;
-}
-
-.max-w-full {
- max-width: 100%;
-}
-
-.max-w-prose {
- max-width: 65ch;
-}
-
-.max-w-xs {
- max-width: 20rem;
-}
-
-.flex-1 {
- flex: 1 1 0%;
-}
-
-.flex-auto {
- flex: 1 1 auto;
-}
-
-.flex-none {
- flex: none;
-}
-
-.flex-shrink-0 {
- flex-shrink: 0;
-}
-
-.shrink-0 {
- flex-shrink: 0;
-}
-
-.flex-grow {
- flex-grow: 1;
-}
-
-.grow {
- flex-grow: 1;
-}
-
-.grow-0 {
- flex-grow: 0;
-}
-
-.-translate-y-1\/2 {
- --tw-translate-y: -50%;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
-.cursor-pointer {
- cursor: pointer;
-}
-
-.resize-none {
- resize: none;
-}
-
-.snap-start {
- scroll-snap-align: start;
-}
-
-.list-inside {
- list-style-position: inside;
-}
-
-.list-disc {
- list-style-type: disc;
-}
-
-.grid-flow-col {
- grid-auto-flow: column;
-}
-
-.grid-cols-1 {
- grid-template-columns: repeat(1, minmax(0, 1fr));
-}
-
-.grid-cols-7 {
- grid-template-columns: repeat(7, minmax(0, 1fr));
-}
-
-.flex-col {
- flex-direction: column;
-}
-
-.flex-wrap {
- flex-wrap: wrap;
-}
-
-.place-items-center {
- place-items: center;
-}
-
-.items-start {
- align-items: flex-start;
-}
-
-.items-center {
- align-items: center;
-}
-
-.justify-normal {
- justify-content: normal;
-}
-
-.justify-end {
- justify-content: flex-end;
-}
-
-.justify-center {
- justify-content: center;
-}
-
-.justify-between {
- justify-content: space-between;
-}
-
-.justify-stretch {
- justify-content: stretch;
-}
-
-.gap-1 {
- gap: 0.25rem;
-}
-
-.gap-2 {
- gap: 0.5rem;
-}
-
-.gap-3 {
- gap: 0.75rem;
-}
-
-.gap-4 {
- gap: 1rem;
-}
-
-.gap-6 {
- gap: 1.5rem;
-}
-
-.space-x-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-right: calc(0.5rem * var(--tw-space-x-reverse));
- margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
-}
-
-.space-x-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-right: calc(1rem * var(--tw-space-x-reverse));
- margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
-}
-
-.space-y-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
-}
-
-.self-start {
- align-self: flex-start;
-}
-
-.overflow-hidden {
- overflow: hidden;
-}
-
-.overflow-scroll {
- overflow: scroll;
-}
-
-.overflow-y-auto {
- overflow-y: auto;
-}
-
-.whitespace-nowrap {
- white-space: nowrap;
-}
-
-.whitespace-pre-wrap {
- white-space: pre-wrap;
-}
-
-.rounded-box {
- border-radius: var(--rounded-box, 1rem);
-}
-
-.rounded-full {
- border-radius: 9999px;
-}
-
-.rounded-lg {
- border-radius: 0.5rem;
-}
-
-.rounded-md {
- border-radius: 0.375rem;
-}
-
-.rounded-e-lg {
- border-start-end-radius: 0.5rem;
- border-end-end-radius: 0.5rem;
-}
-
-.rounded-s-lg {
- border-start-start-radius: 0.5rem;
- border-end-start-radius: 0.5rem;
-}
-
-.border {
- border-width: 1px;
-}
-
-.border-2 {
- border-width: 2px;
-}
-
-.border-x-0 {
- border-left-width: 0px;
- border-right-width: 0px;
-}
-
-.border-b {
- border-bottom-width: 1px;
-}
-
-.border-b-2 {
- border-bottom-width: 2px;
-}
-
-.border-t-2 {
- border-top-width: 2px;
-}
-
-.border-base-200 {
- --tw-border-opacity: 1;
- border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
-}
-
-.border-base-300 {
- --tw-border-opacity: 1;
- border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));
-}
-
-.border-gray-300 {
- --tw-border-opacity: 1;
- border-color: rgb(209 213 219 / var(--tw-border-opacity));
-}
-
-.border-gray-600 {
- --tw-border-opacity: 1;
- border-color: rgb(75 85 99 / var(--tw-border-opacity));
-}
-
-.border-gray-700 {
- --tw-border-opacity: 1;
- border-color: rgb(55 65 81 / var(--tw-border-opacity));
-}
-
-.border-red-500 {
- --tw-border-opacity: 1;
- border-color: rgb(239 68 68 / var(--tw-border-opacity));
-}
-
-.border-slate-300 {
- --tw-border-opacity: 1;
- border-color: rgb(203 213 225 / var(--tw-border-opacity));
-}
-
-.border-white {
- --tw-border-opacity: 1;
- border-color: rgb(255 255 255 / var(--tw-border-opacity));
-}
-
-.border-opacity-100 {
- --tw-border-opacity: 1;
-}
-
-.border-opacity-80 {
- --tw-border-opacity: 0.8;
-}
-
-.bg-\[\#00ff00\] {
- --tw-bg-opacity: 1;
- background-color: rgb(0 255 0 / var(--tw-bg-opacity));
-}
-
-.bg-base-100 {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
-}
-
-.bg-base-200 {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
-}
-
-.bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
-}
-
-.bg-gray-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(229 231 235 / var(--tw-bg-opacity));
-}
-
-.bg-gray-50 {
- --tw-bg-opacity: 1;
- background-color: rgb(249 250 251 / var(--tw-bg-opacity));
-}
-
-.bg-gray-700 {
- --tw-bg-opacity: 1;
- background-color: rgb(55 65 81 / var(--tw-bg-opacity));
-}
-
-.bg-primary {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
-}
-
-.bg-slate-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(241 245 249 / var(--tw-bg-opacity));
-}
-
-.bg-success {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
-}
-
-.bg-opacity-75 {
- --tw-bg-opacity: 0.75;
-}
-
-.bg-opacity-90 {
- --tw-bg-opacity: 0.9;
-}
-
-.bg-gradient-to-t {
- background-image: linear-gradient(to top, var(--tw-gradient-stops));
-}
-
-.from-black\/70 {
- --tw-gradient-from: rgb(0 0 0 / 0.7) var(--tw-gradient-from-position);
- --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
-}
-
-.from-80\% {
- --tw-gradient-from-position: 80%;
-}
-
-.to-transparent {
- --tw-gradient-to: transparent var(--tw-gradient-to-position);
-}
-
-.fill-current {
- fill: currentColor;
-}
-
-.stroke-current {
- stroke: currentColor;
-}
-
-.object-cover {
- -o-object-fit: cover;
- object-fit: cover;
-}
-
-.p-10 {
- padding: 2.5rem;
-}
-
-.p-2 {
- padding: 0.5rem;
-}
-
-.p-3 {
- padding: 0.75rem;
-}
-
-.p-4 {
- padding: 1rem;
-}
-
-.px-1 {
- padding-left: 0.25rem;
- padding-right: 0.25rem;
-}
-
-.px-3 {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
-}
-
-.px-4 {
- padding-left: 1rem;
- padding-right: 1rem;
-}
-
-.px-5 {
- padding-left: 1.25rem;
- padding-right: 1.25rem;
-}
-
-.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-
-.py-2\.5 {
- padding-top: 0.625rem;
- padding-bottom: 0.625rem;
-}
-
-.py-3 {
- padding-top: 0.75rem;
- padding-bottom: 0.75rem;
-}
-
-.py-4 {
- padding-top: 1rem;
- padding-bottom: 1rem;
-}
-
-.pb-24 {
- padding-bottom: 6rem;
-}
-
-.pb-3 {
- padding-bottom: 0.75rem;
-}
-
-.pb-5 {
- padding-bottom: 1.25rem;
-}
-
-.pb-6 {
- padding-bottom: 1.5rem;
-}
-
-.pl-11 {
- padding-left: 2.75rem;
-}
-
-.pl-2 {
- padding-left: 0.5rem;
-}
-
-.pl-3 {
- padding-left: 0.75rem;
-}
-
-.pl-4 {
- padding-left: 1rem;
-}
-
-.pr-2 {
- padding-right: 0.5rem;
-}
-
-.pr-4 {
- padding-right: 1rem;
-}
-
-.text-center {
- text-align: center;
-}
-
-.text-2xl {
- font-size: 1.563rem;
-}
-
-.text-3xl {
- font-size: 1.953rem;
-}
-
-.text-lg {
- font-size: 1.15rem;
-}
-
-.text-sm {
- font-size: 0.8rem;
-}
-
-.text-xl {
- font-size: 1.25rem;
-}
-
-.font-bold {
- font-weight: 700;
-}
-
-.font-medium {
- font-weight: 500;
-}
-
-.font-normal {
- font-weight: 400;
-}
-
-.font-semibold {
- font-weight: 600;
-}
-
-.tracking-wide {
- letter-spacing: 0.025em;
-}
-
-.text-black {
- --tw-text-opacity: 1;
- color: rgb(0 0 0 / var(--tw-text-opacity));
-}
-
-.text-gray-900 {
- --tw-text-opacity: 1;
- color: rgb(17 24 39 / var(--tw-text-opacity));
-}
-
-.text-neutral-content {
- --tw-text-opacity: 1;
- color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
-}
-
-.text-primary {
- --tw-text-opacity: 1;
- color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
-}
-
-.text-primary-content {
- --tw-text-opacity: 1;
- color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
-}
-
-.text-red-500 {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
-}
-
-.text-slate-700 {
- --tw-text-opacity: 1;
- color: rgb(51 65 85 / var(--tw-text-opacity));
-}
-
-.text-slate-700\/50 {
- color: rgb(51 65 85 / 0.5);
-}
-
-.text-white {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
-}
-
-.placeholder-gray-400::-moz-placeholder {
- --tw-placeholder-opacity: 1;
- color: rgb(156 163 175 / var(--tw-placeholder-opacity));
-}
-
-.placeholder-gray-400::placeholder {
- --tw-placeholder-opacity: 1;
- color: rgb(156 163 175 / var(--tw-placeholder-opacity));
-}
-
-.opacity-0 {
- opacity: 0;
-}
-
-.opacity-100 {
- opacity: 1;
-}
-
-.opacity-50 {
- opacity: 0.5;
-}
-
-.shadow {
- --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
-}
-
-.shadow-lg {
- --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
-}
-
-.shadow-md {
- --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
-}
-
-.filter {
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-
-.transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
-}
-
-.transition-\[width\] {
- transition-property: width;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
-}
-
-.transition-all {
- transition-property: all;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- transition-duration: 150ms;
-}
-
-.duration-300 {
- transition-duration: 300ms;
-}
-
-.ease-out {
- transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
-}
-
-@tailwind typography;
-
-@tailwind layout;
-
-body {
- padding-top: 5rem;
-}
-
-body:has(.bottom-drawer) {
- padding-bottom: 10rem;
-}
-
-.sticky-under-top-nav {
- top: 4rem;
-}
-
-.sticky-under-top-nav.subheader {
- top: 8rem;
-}
-
-.sticky-under-top-nav + *:first-child {
- margin-top: 4rem;
-}
-
-.brand-logo {
- width: 3rem;
- height: 4rem;
-}
-
-.brand-type {
- width: 7.7rem;
-}
-
-.drawer-end .drawer-toggle ~ .drawer-side {
- z-index: 999;
-}
-
-.menu .collapse .collapse-title {
- padding: 0;
- min-height: 1.75rem;
-}
-
-.menu .collapse-content {
- padding-left: 0;
- padding-right: 0;
- overflow: initial;
-}
-
-.menu :where(.menu li) {
- flex-wrap: nowrap;
-}
-
-.menu li > *:not(ul, .menu-title, details, .btn):active,
-.menu li > *:not(ul, .menu-title, details, .btn).active,
-.menu li > details > summary:active {
- background-color: transparent;
-}
-
-.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)),
-.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
- gap: 0;
- grid-auto-columns: initial;
- padding-left: 0.5rem;
- padding-right: 0.5rem;
-}
-
-.dropdown-content {
- width: -moz-max-content;
- width: max-content;
-}
-
-.dropdown-content.menu li {
- flex-direction: row;
-}
-
-.dropdown-content.menu .label {
- justify-content: flex-start;
-}
-
-.dropdown-content .collapse-title,
-:where(.dropdown-content .collapse > input[type='checkbox']),
-:where(.dropdown-content .collapse > input[type='radio']) {
- /* min-height: 0; */
- min-height: 1.5rem;
-}
-
-.htmx-indicator {
- display: none;
-}
-
-.htmx-request .htmx-indicator {
- display: inherit;
-}
-
-.htmx-request .htmx-indicator.flex {
- display: flex;
-}
-
-.htmx-show-in-flight {
- display: none;
-}
-
-.htmx-request .htmx-show-in-flight {
- display: inherit;
-}
-
-.htmx-hide-in-flight {
- display: inherit;
-}
-
-.htmx-request .htmx-hide-in-flight {
- display: none;
-}
-
-.margins-when-children.my-8:has(*) {
- margin: 2rem 0;
-}
-
-/* BEGIN seshu ingestion "add event source" section */
-
-#event-source-steps .step {
- min-width: 15rem;
-}
-
-.checkbox-card {
- border: 2px solid oklch(var(--er));
-}
-
-.checkbox-card .checkbox-card-header {
- border-top-left-radius: var(--rounded-btn);
- border-top-right-radius: var(--rounded-btn);
-}
-
-.checkbox-card .checkbox-card-header .label {
- justify-content: center;
-}
-
-.checkbox-card .checkbox-card-header:has(input[type='checkbox']) {
- background-color: oklch(var(--er));
- color: oklch(var(--erc));
-}
-
-.checkbox-card .checkbox-card-header:has(input[type='checkbox']:checked) {
- background-color: oklch(var(--su));
- color: oklch(var(--suc));
-}
-
-.checkbox-card:has(input[id*='main-toggle-'][type='checkbox']:checked) {
- border-color: oklch(var(--su));
-}
-
-.has-toggleable-text:has(input[type='checkbox']) .hidden-when-checked {
- display: inherit;
-}
-
-.has-toggleable-text:has(input[type='checkbox']:checked) .hidden-when-checked {
- display: none;
-}
-
-.has-toggleable-text:not(:has(input[type='checkbox']:checked))
- .hidden-when-not-checked {
- display: none;
-}
-
-#event-source-container:not(:has(#event-candidates-inner .checkbox-card))
- .candidates-loaded-visible,
-#event-source-container:has(#event-candidates-inner .checkbox-card)
- .candidates-loading-visible {
- height: 0;
- width: 0;
- opacity: 0;
- display: none;
-}
-
-#event-source-container:has(#event-candidates-inner .checkbox-card)
- #explainer-section
- .alert-info {
- background-color: oklch(var(--su));
-}
-
-/* END seshu ingestion "add event source" section */
-
-.bottom-drawer {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- width: 100%;
- z-index: 999;
- height: auto;
- /* TODO: FIX THIS! use tailwind variables */
- background: white;
- padding: 20px;
-}
-
-.btn.carousel-control-left:active:hover, .btn.carousel-control-left:active:focus {
- transform: translate(-100%, -50%)
-}
-
-.btn.carousel-control-right:active:hover, .btn.carousel-control-right:active:focus {
- transform: translate(100%, -50%)
-}
-
-.btn.btn-bold-outline {
- border: 5px solid oklch(var(--p));
-}
-
-.btn-outline.btn-primary.text-neutral-content {
- color: var(--fallback-nc, oklch(var(--nc) / var(--tw-text-opacity)));
-}
-
-.drawer .collapse-content :where(.menu li) {
- flex-direction: inherit;
- align-items: center;
-}
-
-/* Chrome, Safari, Edge, Opera */
-
-input::-webkit-outer-spin-button,
-input::-webkit-inner-spin-button {
- -webkit-appearance: none;
- margin: 0;
-}
-
-/* Firefox */
-
-input[type='number'] {
- -moz-appearance: textfield;
-}
-
-.tab:is(input[type='radio']) {
- width: -moz-max-content;
- width: max-content;
-}
-
-.header-hero .opener {
- font-size: 125%;
-}
-
-.icon-container {
- display: inline-flex;
- justify-content: center;
- vertical-align: middle;
-}
-
-.page-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1.5rem;
-}
-
-.table.top-align :where(th, td) {
- vertical-align: top;
-}
-
-@media (min-width: 640px) {
- .sm\:modal-middle {
- place-items: center;
- }
-
- .sm\:modal-middle :where(.modal-box) {
- width: 91.666667%;
- max-width: 32rem;
- --tw-translate-y: 0px;
- --tw-scale-x: .9;
- --tw-scale-y: .9;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
- border-top-left-radius: var(--rounded-box, 1rem);
- border-top-right-radius: var(--rounded-box, 1rem);
- border-bottom-right-radius: var(--rounded-box, 1rem);
- border-bottom-left-radius: var(--rounded-box, 1rem);
- }
-}
-
-.last\:mr-0:last-child {
- margin-right: 0px;
-}
-
-.hover\:bg-gray-500:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(107 114 128 / var(--tw-bg-opacity));
-}
-
-.hover\:bg-gray-600:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(75 85 99 / var(--tw-bg-opacity));
-}
-
-.hover\:bg-slate-800\/5:hover {
- background-color: rgb(30 41 59 / 0.05);
-}
-
-.hover\:bg-opacity-80:hover {
- --tw-bg-opacity: 0.8;
-}
-
-.hover\:text-black:hover {
- --tw-text-opacity: 1;
- color: rgb(0 0 0 / var(--tw-text-opacity));
-}
-
-.hover\:opacity-75:hover {
- opacity: 0.75;
-}
-
-.focus\:outline-none:focus {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.focus\:ring-2:focus {
- --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
- --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
- box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
-}
-
-.focus\:ring-gray-700:focus {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(55 65 81 / var(--tw-ring-opacity));
-}
-
-.focus-visible\:border-green-700:focus-visible {
- --tw-border-opacity: 1;
- border-color: rgb(21 128 61 / var(--tw-border-opacity));
-}
-
-.focus-visible\:bg-slate-800\/5:focus-visible {
- background-color: rgb(30 41 59 / 0.05);
-}
-
-.focus-visible\:text-black:focus-visible {
- --tw-text-opacity: 1;
- color: rgb(0 0 0 / var(--tw-text-opacity));
-}
-
-.focus-visible\:outline-none:focus-visible {
- outline: 2px solid transparent;
- outline-offset: 2px;
-}
-
-.focus-visible\:outline:focus-visible {
- outline-style: solid;
-}
-
-.focus-visible\:outline-2:focus-visible {
- outline-width: 2px;
-}
-
-.focus-visible\:outline-offset-2:focus-visible {
- outline-offset: 2px;
-}
-
-.focus-visible\:outline-green-700:focus-visible {
- outline-color: #15803d;
-}
-
-.disabled\:cursor-not-allowed:disabled {
- cursor: not-allowed;
-}
-
-.disabled\:opacity-75:disabled {
- opacity: 0.75;
-}
-
-.peer:checked ~ .peer-checked\:bg-base-200 {
- --tw-bg-opacity: 1;
- background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
-}
-
-@media (min-width: 768px) {
- .md\:float-start {
- float: inline-start;
- }
-
- .md\:mb-3 {
- margin-bottom: 0.75rem;
- }
-
- .md\:mr-5 {
- margin-right: 1.25rem;
- }
-
- .md\:grid {
- display: grid;
- }
-
- .md\:aspect-\[16\/9\] {
- aspect-ratio: 16/9;
- }
-
- .md\:w-1\/2 {
- width: 50%;
- }
-
- .md\:grid-cols-2 {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .md\:grid-cols-7 {
- grid-template-columns: repeat(7, minmax(0, 1fr));
- }
-
- .md\:place-items-center {
- place-items: center;
- }
-
- .md\:p-12 {
- padding: 3rem;
- }
-
- .md\:pb-4 {
- padding-bottom: 1rem;
- }
-
- .md\:pr-3 {
- padding-right: 0.75rem;
- }
-
- .md\:text-3xl {
- font-size: 1.953rem;
- }
-
- .md\:text-4xl {
- font-size: 2.441rem;
- }
-
- .md\:text-base {
- font-size: 1rem;
- }
-
- .md\:text-lg {
- font-size: 1.15rem;
- }
-
- .md\:text-xl {
- font-size: 1.25rem;
- }
-
- .md\:opacity-30 {
- opacity: 0.3;
- }
-
- .md\:hover\:opacity-75:hover {
- opacity: 0.75;
- }
-}
-
-@media (min-width: 1024px) {
- .lg\:col-span-2 {
- grid-column: span 2 / span 2;
- }
-
- .lg\:col-span-5 {
- grid-column: span 5 / span 5;
- }
-
- .lg\:inline-block {
- display: inline-block;
- }
-
- .lg\:flex {
- display: flex;
- }
-
- .lg\:inline-flex {
- display: inline-flex;
- }
-
- .lg\:hidden {
- display: none;
- }
-
- .lg\:w-1\/3 {
- width: 33.333333%;
- }
-
- .lg\:grid-cols-3 {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
-
- .lg\:grid-cols-7 {
- grid-template-columns: repeat(7, minmax(0, 1fr));
- }
-}
-
-@media (prefers-color-scheme: dark) {
- .dark\:border-slate-700 {
- --tw-border-opacity: 1;
- border-color: rgb(51 65 85 / var(--tw-border-opacity));
- }
-
- .dark\:bg-slate-800 {
- --tw-bg-opacity: 1;
- background-color: rgb(30 41 59 / var(--tw-bg-opacity));
- }
-
- .dark\:bg-slate-800\/50 {
- background-color: rgb(30 41 59 / 0.5);
- }
-
- .dark\:text-slate-300 {
- --tw-text-opacity: 1;
- color: rgb(203 213 225 / var(--tw-text-opacity));
- }
-
- .dark\:text-slate-300\/50 {
- color: rgb(203 213 225 / 0.5);
- }
-
- .dark\:hover\:bg-slate-100\/5:hover {
- background-color: rgb(241 245 249 / 0.05);
- }
-
- .dark\:hover\:text-white:hover {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
- }
-
- .dark\:focus-visible\:border-green-600:focus-visible {
- --tw-border-opacity: 1;
- border-color: rgb(22 163 74 / var(--tw-border-opacity));
- }
-
- .dark\:focus-visible\:bg-slate-100\/10:focus-visible {
- background-color: rgb(241 245 249 / 0.1);
- }
-
- .dark\:focus-visible\:text-white:focus-visible {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
- }
-
- .dark\:focus-visible\:outline-green-600:focus-visible {
- outline-color: #16a34a;
- }
-}