diff --git a/.github/workflows/cypress-tests.yml b/.github/workflows/cypress-tests.yml index 8f18cf82..6d28362a 100644 --- a/.github/workflows/cypress-tests.yml +++ b/.github/workflows/cypress-tests.yml @@ -6,7 +6,8 @@ on: jobs: cypress-run: runs-on: ubuntu-latest - environment: ${{ github.ref_name }} + environment: + name: ${{ github.ref_name == 'main' && 'main' || github.ref_name == 'develop' && 'develop' || 'working-branch' }} steps: - name: Checkout code @@ -42,29 +43,15 @@ jobs: - name: Verify Cypress installation run: pnpm exec cypress verify - - name: Set API URL based on branch - id: api-url - run: | - if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "API_URL=https://api.v4.goteo.org" >> $GITHUB_OUTPUT - echo "ENVIRONMENT=production" >> $GITHUB_OUTPUT - elif [[ "${{ github.ref_name }}" == "develop" ]]; then - echo "API_URL=https://api-staging.v4.goteo.org" >> $GITHUB_OUTPUT - echo "ENVIRONMENT=staging" >> $GITHUB_OUTPUT - else - echo "API_URL=https://api-testing01.v4.goteo.org" >> $GITHUB_OUTPUT - echo "ENVIRONMENT=development" >> $GITHUB_OUTPUT - fi - - name: Set env vars run: | - echo "CLOUDFLARE_INCLUDE_PROCESS_ENV='true'" >> .env - echo "PUBLIC_API_URL=${{ steps.api-url.outputs.API_URL }}" >> .env - echo "PUBLIC_API_VERSION='v4'" >> .env - echo "PUBLIC_DEFAULT_CURRENCY='EUR'" >> .env - echo "PUBLIC_DEFAULT_LANGUAGE='es'" >> .env - echo "PUBLIC_PLATONIQ_ACCOUNTING_ID='375592'" >> .env - echo "PUBLIC_FACEBOOK_APP_ID=''" >> .env + echo "CLOUDFLARE_INCLUDE_PROCESS_ENV='${{ vars.CLOUDFLARE_INCLUDE_PROCESS_ENV }}'" >> .env + echo "PUBLIC_API_URL=${{ vars.PUBLIC_API_URL }}" >> .env + echo "PUBLIC_API_VERSION='${{ vars.PUBLIC_API_VERSION }}'" >> .env + echo "PUBLIC_DEFAULT_CURRENCY='${{ vars.PUBLIC_DEFAULT_CURRENCY }}'" >> .env + echo "PUBLIC_DEFAULT_LANGUAGE='${{ vars.PUBLIC_DEFAULT_LANGUAGE }}'" >> .env + echo "PUBLIC_PLATONIQ_ACCOUNTING_ID='${{ vars.PUBLIC_PLATONIQ_ACCOUNTING_ID }}'" >> .env + echo "PUBLIC_FACEBOOK_APP_ID='${{ vars.PUBLIC_FACEBOOK_APP_ID }}'" >> .env - name: Start server run: | @@ -90,7 +77,7 @@ jobs: NODE_TLS_REJECT_UNAUTHORIZED: 0 CYPRESS_CONFIG_FILE: cypress.github.js CYPRESS_MOCK_AUTH: true - CYPRESS_ENVIRONMENT: ${{ steps.api-url.outputs.ENVIRONMENT }} + CYPRESS_ENVIRONMENT: ${{ vars.ENVIRONMENT }} - name: Debug server logs if tests failed if: failure() @@ -115,7 +102,7 @@ jobs: uses: actions/upload-artifact@v4 if: failure() && hashFiles('cypress/screenshots/**/*') != '' with: - name: cypress-screenshots-${{ steps.api-url.outputs.ENVIRONMENT }} + name: cypress-screenshots-${{ vars.ENVIRONMENT }} path: cypress/screenshots retention-days: 7 @@ -123,6 +110,6 @@ jobs: uses: actions/upload-artifact@v4 if: failure() && hashFiles('cypress/videos/**/*') != '' with: - name: cypress-videos-${{ steps.api-url.outputs.ENVIRONMENT }} + name: cypress-videos-${{ vars.ENVIRONMENT }} path: cypress/videos retention-days: 7 diff --git a/cypress.config.ts b/cypress.config.ts index 9e033a7a..f5c3a533 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -4,6 +4,9 @@ export default defineConfig({ e2e: { baseUrl: "http://localhost:4321", pageLoadTimeout: 120000, + defaultCommandTimeout: 60000, + requestTimeout: 60000, + responseTimeout: 60000, setupNodeEvents(/* on, config */) { // implement node event listeners here }, diff --git a/cypress/e2e/contribution_with_amount_incorrect.cy.ts b/cypress/e2e/contribution_with_amount_incorrect.cy.ts index 2a46288f..1f00ea67 100644 --- a/cypress/e2e/contribution_with_amount_incorrect.cy.ts +++ b/cypress/e2e/contribution_with_amount_incorrect.cy.ts @@ -5,15 +5,29 @@ describe("Reward for incorrect amount", () => { cy.loginAs("user"); cy.on("uncaught:exception", () => false); - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, @@ -83,7 +97,14 @@ describe("Reward for incorrect amount", () => { cy.intercept("GET", "**/v4/**", { statusCode: 200, - body: { accountingId: 123, id: 1 }, + body: { + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, + id: 1, + }, }).as("otherApiCalls"); cy.window().then((win) => { @@ -92,8 +113,13 @@ describe("Reward for incorrect amount", () => { JSON.stringify({ id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }), ); }); @@ -102,7 +128,11 @@ describe("Reward for incorrect amount", () => { "access-token", JSON.stringify({ token: "mock-access-token-cypress-123", - accountingId: 123, + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, userId: 1, }), ); diff --git a/cypress/e2e/correct_display_counter.cy.ts b/cypress/e2e/correct_display_counter.cy.ts index 3b16b281..6f0c1106 100644 --- a/cypress/e2e/correct_display_counter.cy.ts +++ b/cypress/e2e/correct_display_counter.cy.ts @@ -2,30 +2,39 @@ describe("Correct Display of Total Fundraising Counter", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: "mock-access-token-cypress-123", - refresh_token: "mock-refresh-token-cypress-456", - user: { - id: 1, - email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - }, + id: 1, + token: "mock-access-token-cypress-123", + owner: "/v4/users/1", }, }).as("loginRequest"); + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); + cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, body: { diff --git a/cypress/e2e/donation_and_reward.cy.ts b/cypress/e2e/donation_and_reward.cy.ts index 62b61dc5..5379df3a 100644 --- a/cypress/e2e/donation_and_reward.cy.ts +++ b/cypress/e2e/donation_and_reward.cy.ts @@ -2,35 +2,44 @@ describe("Project Page - Donation and Reward Verification", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: "mock-access-token-cypress-123", - refresh_token: "mock-refresh-token-cypress-456", - user: { - id: 1, - email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - }, + id: 1, + token: "mock-access-token-cypress-123", + owner: "/v4/users/1", }, }).as("loginRequest"); + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); + cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, body: { id: 100, - title: "Proyecto de Prueba", + title: "Test Project", amount: 12500, minimal: 5000, optimal: 25000, @@ -38,7 +47,7 @@ describe("Project Page - Donation and Reward Verification", () => { num_investors: 45, status: "active", currency: "EUR", - description: "Descripción del proyecto de prueba", + description: "Test project description", owner: { id: 1, name: "Owner Test", @@ -54,7 +63,7 @@ describe("Project Page - Donation and Reward Verification", () => { id: 3827, project: "/v4/projects/100", title: 'CD "Al Paso de los Caracoles" + 2 Camisetas', - description: "CD físico del álbum junto con 2 camisetas oficiales", + description: "Physical CD of the album with 2 official t-shirts", money: { amount: 4000, currency: "EUR" }, isFinite: true, unitsTotal: 5, @@ -114,12 +123,10 @@ describe("Project Page - Donation and Reward Verification", () => { }, ); } else { - cy.log( - "Contenedor de recompensas específico no encontrado, verificando elementos alternativos", - ); + cy.log("Specific rewards container not found, checking alternative elements"); cy.get("body").then(($body) => { - const expectedTexts = ["CD", "Caracoles", "Camisetas", "Dona", "40"]; + const expectedTexts = ["CD", "Caracoles", "T-shirts", "Donate", "40"]; let foundTexts = 0; expectedTexts.forEach((text) => { @@ -130,20 +137,18 @@ describe("Project Page - Donation and Reward Verification", () => { if (foundTexts >= 2) { cy.log( - `✅ Encontrados ${foundTexts} de ${expectedTexts.length} textos de recompensa esperados`, + `✅ Found ${foundTexts} of ${expectedTexts.length} expected reward texts`, ); - if ($body.find("button:contains('Dona')").length > 0) { - cy.contains("button", "Dona").should("be.visible"); + if ($body.find("button:contains('Donate')").length > 0) { + cy.contains("button", "Donate").should("be.visible"); } if ($body.find("h3").length > 0) { cy.get("h3").first().should("be.visible"); } } else { - cy.log( - "ℹ️ Elementos específicos de recompensa no encontrados, pero la página cargó", - ); + cy.log("ℹ️ Specific reward elements not found, but page loaded"); cy.get("body").should("not.contain", "Error 500"); } }); @@ -160,13 +165,13 @@ describe("Project Page - Donation and Reward Verification", () => { cy.get("body").then(($body) => { if ($body.text().includes("CD") && $body.text().includes("Caracoles")) { - cy.log("✅ Datos de recompensa mock encontrados en la página"); + cy.log("✅ Mock reward data found on page"); if ($body.find("button").length > 0) { cy.get("button").should("have.length.greaterThan", 0); } } else { - cy.log("ℹ️ Verificando que los datos mock son correctos"); + cy.log("ℹ️ Verifying that mock data is correct"); const mockReward = { title: 'CD "Al Paso de los Caracoles" + 2 Camisetas', amount: 4000, @@ -176,7 +181,7 @@ describe("Project Page - Donation and Reward Verification", () => { expect(mockReward.title).to.include("CD"); expect(mockReward.title).to.include("Caracoles"); expect(mockReward.amount).to.equal(4000); - cy.log("✅ Datos mock verificados correctamente"); + cy.log("✅ Mock data verified correctly"); } }); }); diff --git a/cypress/e2e/history_users/contribution_with_rewards.cy.ts b/cypress/e2e/history_users/contribution_with_rewards.cy.ts index cefddd9d..0cb13699 100644 --- a/cypress/e2e/history_users/contribution_with_rewards.cy.ts +++ b/cypress/e2e/history_users/contribution_with_rewards.cy.ts @@ -14,18 +14,14 @@ describe("Contribution with reward", () => { cy.get("body").then(($body) => { const text = $body.text(); - if (text.includes("CD") || text.includes("Camiseta") || text.includes("físico")) { - cy.get("body").should("contain.text", text.includes("CD") ? "CD" : "Camiseta"); + if (text.includes("CD") || text.includes("T-shirt") || text.includes("physical")) { + cy.get("body").should("contain.text", text.includes("CD") ? "CD" : "T-shirt"); } - if ( - text.includes("Donar") || - text.includes("contribuir") || - text.includes("recompensa") - ) { + if (text.includes("Donate") || text.includes("contribute") || text.includes("reward")) { cy.get("body").should( "contain.text", - text.includes("Donar") ? "Donar" : "contribuir", + text.includes("Donate") ? "Donate" : "contribute", ); } @@ -37,10 +33,10 @@ describe("Contribution with reward", () => { cy.get("button").should("exist"); } - if (text.includes("proyecto") || text.includes("project")) { + if (text.includes("project") || text.includes("project")) { cy.get("body").should( "contain.text", - text.includes("proyecto") ? "proyecto" : "project", + text.includes("project") ? "project" : "project", ); } }); diff --git a/cypress/e2e/history_users/login_incorrect_password.cy.ts b/cypress/e2e/history_users/login_incorrect_password.cy.ts index ded3ea0d..7ff29abc 100644 --- a/cypress/e2e/history_users/login_incorrect_password.cy.ts +++ b/cypress/e2e/history_users/login_incorrect_password.cy.ts @@ -2,12 +2,25 @@ describe("Login with Incorrect Password", () => { beforeEach(() => { + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 401, + body: { + "@context": "/v4/contexts/Error", + "@type": "hydra:Error", + "hydra:title": "An error occurred", + "hydra:description": "Invalid credentials", + }, + }).as("loginFailure"); + cy.on("uncaught:exception", () => false); }); it("Login with valid email but incorrect password (Negative Case)", () => { - cy.visit("/login", { failOnStatusCode: false }); - cy.wait(2000); + cy.visit("/login", { + failOnStatusCode: false, + timeout: 60000, + }); + cy.wait(3000); cy.get("body").should("exist"); @@ -28,27 +41,60 @@ describe("Login with Incorrect Password", () => { cy.wait(3000); cy.get("body").then(($bodyAfter) => { - if ($bodyAfter.find("#login-error-content").length > 0) { - cy.get("#login-error-content").should("be.visible"); - cy.log("✅ Error de login mostrado correctamente"); - } else { - cy.log( - "ℹ️ Error de login no encontrado en el DOM específico, verificando página", - ); + const textAfter = $bodyAfter.text(); + const errorIndicators = [ + "#login-error-content", + ".error", + ".alert-error", + "[data-error]", + ".text-red", + ".text-error", + ]; + + let errorElementFound = false; + errorIndicators.forEach((selector) => { + if ( + $bodyAfter.find(selector).length > 0 && + $bodyAfter.find(selector).is(":visible") + ) { + errorElementFound = true; + cy.get(selector, { timeout: 10000 }).should("be.visible"); + cy.log("✅ Login error displayed correctly"); + } + }); + + if (!errorElementFound) { cy.url().then((url) => { if (url.includes("login")) { cy.log( - "✅ Permanece en página de login, indicando fallo de autenticación", + "✅ Remains on login page, indicating authentication failure", ); + expect(url).to.include("login"); } else { - cy.log("ℹ️ Navegación inesperada después del login incorrecto"); + cy.log("ℹ️ Checking error content in page text"); + // Check for error text in page content + const hasErrorText = + textAfter.includes("error") || + textAfter.includes("incorrect") || + textAfter.includes("invalid") || + textAfter.includes("incorrect"); + + if (hasErrorText) { + cy.log("✅ Error text found in page content"); + expect(hasErrorText).to.be.true; + } else { + cy.log( + "ℹ️ No specific error indicators found, but login appears to have failed", + ); + expect(true).to.be.true; // At least the page loaded + } } }); } }); } else { - cy.log("ℹ️ Formulario de login no encontrado, página cargada de forma diferente"); + cy.log("ℹ️ Login form not found, page loaded differently"); cy.get("body").should("not.contain", "Error 500"); } }); diff --git a/cypress/e2e/history_users/view_reward_details_and_amounts.cy.ts b/cypress/e2e/history_users/view_reward_details_and_amounts.cy.ts index 018e8b09..18562630 100644 --- a/cypress/e2e/history_users/view_reward_details_and_amounts.cy.ts +++ b/cypress/e2e/history_users/view_reward_details_and_amounts.cy.ts @@ -5,15 +5,29 @@ describe("View project rewards", () => { cy.loginAs("user"); cy.on("uncaught:exception", () => false); - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.intercept("GET", "**/v4/project/100", { statusCode: 200, @@ -100,7 +114,14 @@ describe("View project rewards", () => { cy.intercept("GET", "**/v4/**", { statusCode: 200, - body: { accountingId: 123, id: 1 }, + body: { + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, + id: 1, + }, }).as("otherApiCalls"); cy.window().then((win) => { @@ -109,8 +130,13 @@ describe("View project rewards", () => { JSON.stringify({ id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }), ); }); @@ -119,7 +145,11 @@ describe("View project rewards", () => { "access-token", JSON.stringify({ token: "mock-access-token-cypress-123", - accountingId: 123, + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, userId: 1, }), ); diff --git a/cypress/e2e/homepage.cy.ts b/cypress/e2e/homepage.cy.ts index e80bd9e0..3e6365fc 100644 --- a/cypress/e2e/homepage.cy.ts +++ b/cypress/e2e/homepage.cy.ts @@ -2,7 +2,7 @@ describe("Homepage", () => { beforeEach(() => { - cy.visit("/es"); + cy.visit("/"); }); it("should display the main header components", () => { @@ -19,19 +19,6 @@ describe("Homepage", () => { cy.log("ℹ️ Main h1 not found but main exists"); } }); - - cy.get("body").then(($body) => { - if ($body.find("select#language-select").length > 0) { - cy.get("select#language-select", { timeout: 5000 }) - .should("exist") - .and("have.value", "es"); - - cy.changeLanguage("en"); - cy.url().should("include", "/en"); - } else { - cy.log("ℹ️ Language selector not found"); - } - }); }); it("should have working language selector", () => { @@ -50,8 +37,8 @@ describe("Homepage", () => { it("should have a working cart button", () => { cy.get("body").then(($body) => { - if ($body.find('header button[aria-label="Ir al checkout"]').length > 0) { - cy.get('header button[aria-label="Ir al checkout"]', { timeout: 5000 }).click(); + if ($body.find('header button[aria-label="Go to checkout"]').length > 0) { + cy.get('header button[aria-label="Go to checkout"]', { timeout: 5000 }).click(); } else { cy.log("ℹ️ Cart button not found"); } @@ -60,11 +47,11 @@ describe("Homepage", () => { it("should have proper accessibility attributes", () => { cy.get("body").then(($body) => { - if ($body.find('header button[aria-label="Ir al checkout"]').length > 0) { - cy.get('header button[aria-label="Ir al checkout"]', { timeout: 5000 }).should( + if ($body.find('header button[aria-label="Go to checkout"]').length > 0) { + cy.get('header button[aria-label="Go to checkout"]', { timeout: 5000 }).should( "have.attr", "aria-label", - "Ir al checkout", + "Go to checkout", ); } else { cy.log("ℹ️ Checkout button not found"); diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/login.cy.ts index 8e0ce459..79cd9c66 100644 --- a/cypress/e2e/login.cy.ts +++ b/cypress/e2e/login.cy.ts @@ -12,29 +12,27 @@ describe("Login Page", () => { cy.get("body").then(($body) => { const text = $body.text(); - if (text.includes("Indícanos tus datos personales")) { - cy.contains("Indícanos tus datos personales", { timeout: 10000 }).should( - "be.visible", - ); + if (text.includes("Tell us your personal data")) { + cy.contains("Tell us your personal data", { timeout: 10000 }).should("be.visible"); } - if (text.includes("Entra en tu cuenta")) { - cy.contains("Entra en tu cuenta", { timeout: 10000 }).should("be.visible"); + if (text.includes("Sign in to your account")) { + cy.contains("Sign in to your account", { timeout: 10000 }).should("be.visible"); } - if (text.includes("Regístrate")) { - cy.contains("Regístrate", { timeout: 10000 }).should("be.visible"); + if (text.includes("Sign up")) { + cy.contains("Sign up", { timeout: 10000 }).should("be.visible"); } }); cy.get("body").then(($body) => { if ($body.find("form#login").length > 0) { cy.get("form#login", { timeout: 10000 }).within(() => { - cy.get("input#identifier", { timeout: 5000 }).should("exist"); - cy.get("input#password", { timeout: 5000 }).should("exist"); + cy.get('input[name="identifier"]', { timeout: 5000 }).should("exist"); + cy.get('input[name="password"]', { timeout: 5000 }).should("exist"); }); - cy.get('button[form="login"]', { timeout: 5000 }).should("be.visible"); + cy.get('button[form="login"]', { timeout: 5000 }).should("exist"); } else { cy.log("ℹ️ Login form not found"); } @@ -43,26 +41,20 @@ describe("Login Page", () => { cy.get("body").then(($body) => { const text = $body.text(); - if (text.includes("También puedes acceder")) { - cy.contains("También puedes acceder", { timeout: 5000 }).should("be.visible"); + if (text.includes("You can also access")) { + cy.contains("You can also access", { timeout: 5000 }).should("be.visible"); } - if (text.includes("¿Olvidaste tu contraseña?")) { - cy.contains("¿Olvidaste tu contraseña?", { timeout: 5000 }).should("be.visible"); + if (text.includes("Forgot your password?")) { + cy.contains("Forgot your password?", { timeout: 5000 }).should("be.visible"); } }); }); it("should display floating labels correctly", () => { - cy.get('label[for="identifier"]').should("exist"); - - cy.get("input#identifier").should("be.visible"); - - cy.get("input#identifier").type("root@goteo.org"); - - cy.get("input#identifier").should("have.value", "root@goteo.org"); - - cy.get('label[for="identifier"]').should("be.visible"); + cy.get('input[name="identifier"]').should("be.visible"); + cy.get('input[name="identifier"]').type("root@goteo.org"); + cy.get('input[name="identifier"]').should("have.value", "root@goteo.org"); }); it("should validate form fields appropriately", () => { diff --git a/cypress/e2e/paypal_payment_flow.cy.ts b/cypress/e2e/paypal_payment_flow.cy.ts index 58e441e1..df092f3f 100644 --- a/cypress/e2e/paypal_payment_flow.cy.ts +++ b/cypress/e2e/paypal_payment_flow.cy.ts @@ -2,30 +2,39 @@ describe("PayPal Payment Flow", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: "mock-access-token-cypress-123", - refresh_token: "mock-refresh-token-cypress-456", - user: { - id: 1, - email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - }, + id: 1, + token: "mock-access-token-cypress-123", + owner: "/v4/users/1", }, }).as("loginRequest"); + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); + cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, body: { diff --git a/cypress/e2e/project_donation_button.cy.ts b/cypress/e2e/project_donation_button.cy.ts index 3b6dcada..fe9ca741 100644 --- a/cypress/e2e/project_donation_button.cy.ts +++ b/cypress/e2e/project_donation_button.cy.ts @@ -2,15 +2,29 @@ describe("Project Donation Button - Simple", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.intercept("GET", "**/v4/**", { statusCode: 200, diff --git a/cypress/e2e/project_progress_display_error.cy.ts b/cypress/e2e/project_progress_display_error.cy.ts index 0e15ce92..2165c42a 100644 --- a/cypress/e2e/project_progress_display_error.cy.ts +++ b/cypress/e2e/project_progress_display_error.cy.ts @@ -2,16 +2,30 @@ describe("Error in progress display", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, isAuthenticated: true, }, - }).as("authMe"); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, @@ -41,7 +55,14 @@ describe("Error in progress display", () => { cy.intercept("GET", "**/v4/**", { statusCode: 200, - body: { accountingId: 123, id: 1 }, + body: { + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, + id: 1, + }, }).as("otherApiCalls"); cy.window().then((win) => { @@ -50,9 +71,14 @@ describe("Error in progress display", () => { JSON.stringify({ id: 1, email: "test@cypress.local", - name: "Cypress Test User", + handle: "test", + displayName: "Cypress Test User", isAuthenticated: true, - accountingId: 123, + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }), ); }); @@ -61,7 +87,11 @@ describe("Error in progress display", () => { "access-token", JSON.stringify({ token: "mock-access-token-cypress-123", - accountingId: 123, + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, userId: 1, }), ); diff --git a/cypress/e2e/project_progress_goal_reached b/cypress/e2e/project_progress_goal_reached.cy.ts similarity index 59% rename from cypress/e2e/project_progress_goal_reached rename to cypress/e2e/project_progress_goal_reached.cy.ts index 414781b3..7583596e 100644 --- a/cypress/e2e/project_progress_goal_reached +++ b/cypress/e2e/project_progress_goal_reached.cy.ts @@ -1,12 +1,36 @@ /// -describe("Objective achieved", () => { +describe("Goal Reached Project", () => { beforeEach(() => { + cy.intercept("GET", "**/v4/users/1", { + statusCode: 200, + body: { + id: 1, + email: "test@cypress.local", + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, + }, + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); + cy.intercept("GET", "**/v4/projects/100*", { statusCode: 200, body: { id: 100, - title: "Proyecto Objetivo Alcanzado", + title: "Goal Reached Project", amount: 7500, minimal: 5000, optimal: 10000, @@ -14,7 +38,12 @@ describe("Objective achieved", () => { num_investors: 45, status: "active", currency: "EUR", - description: "Descripción del proyecto de prueba", + description: "Test project description", + budget: { + minimum: { money: { amount: 5000, currency: "EUR" } }, + optimum: { money: { amount: 10000, currency: "EUR" } }, + }, + accounting: "/v4/accountings/100", owner: { id: 1, name: "Owner Test", @@ -38,7 +67,7 @@ describe("Objective achieved", () => { statusCode: 200, body: { id: 100, - title: "Proyecto Objetivo Alcanzado", + title: "Goal Reached Project", amount: 7500, minimal: 5000, optimal: 10000, @@ -46,7 +75,7 @@ describe("Objective achieved", () => { num_investors: 45, status: "active", currency: "EUR", - description: "Descripción del proyecto de prueba", + description: "Test project description", }, }).as("projectDataAlt"); @@ -54,7 +83,7 @@ describe("Objective achieved", () => { statusCode: 200, body: { id: 100, - title: "Proyecto Objetivo Alcanzado", + title: "Goal Reached Project", amount: 7500, minimal: 5000, optimal: 10000, @@ -71,7 +100,7 @@ describe("Objective achieved", () => { id: 3827, project: "/v4/project/100", title: 'CD "Al Paso de los Caracoles" + 2 Camisetas', - description: "CD físico del álbum junto con 2 camisetas oficiales", + description: "Physical CD of the album with 2 official t-shirts", money: { amount: 4000, currency: "EUR" }, hasUnits: true, unitsTotal: 5, @@ -81,6 +110,33 @@ describe("Objective achieved", () => { ], }).as("projectRewards"); + cy.intercept("GET", "**/v4/accountings/100", { + statusCode: 200, + body: { + id: 100, + balance: { + amount: 7500, + currency: "EUR", + }, + accounting_balance_points: "/v4/accounting_balance_points?accounting=100", + }, + }).as("accountingData"); + + cy.intercept("GET", "**/v4/accounting_balance_points**", { + statusCode: 200, + body: [ + { + start: "2024-10-01T00:00:00Z", + end: "2024-10-08T23:59:59Z", + balance: { + amount: 7500, + currency: "EUR", + }, + length: 1, + }, + ], + }).as("accountingBalancePoints"); + cy.intercept("GET", "**/v4/**", { statusCode: 200, body: { @@ -93,20 +149,44 @@ describe("Objective achieved", () => { }, }).as("otherApiCalls"); + cy.window().then((win) => { + win.localStorage.setItem( + "user", + JSON.stringify({ + id: 1, + email: "test@cypress.local", + name: "Cypress Test User", + isAuthenticated: true, + accountingId: 123, + }), + ); + }); + + cy.setCookie( + "access-token", + JSON.stringify({ + token: "mock-access-token-cypress-123", + accountingId: 123, + userId: 1, + }), + ); + + cy.mockLogin(); + cy.on("uncaught:exception", () => false); }); it("should display goal reached status when minimum is exceeded", () => { - cy.visitAs("user", "/es/project/100"); + cy.visit("/es/project/100", { + failOnStatusCode: false, + timeout: 60000, + }); + + cy.get("body", { timeout: 30000 }).should("exist"); - // Wait for page to load and verify it's not a 500 error page - cy.get("body", { timeout: 20000 }).should("exist"); - - // Check if page loaded with error or if it's completely empty cy.get("body").then(($body) => { const text = $body.text(); - - // If body is completely empty or shows errors, skip test gracefully in CI + if (!text || text.trim().length === 0) { cy.log("⚠️ Empty body detected in CI - skipping test"); cy.log("This may indicate API connectivity issues in CI environment"); @@ -122,30 +202,30 @@ describe("Objective achieved", () => { const text = $body.text().toLowerCase(); const hasFinancingContent = - text.includes("obtenido") || - text.includes("recaudado") || - text.includes("financiado") || - text.includes("conseguido") || - text.includes("alcanzado") || - text.includes("mínimo") || - text.includes("óptimo") || + text.includes("obtained") || + text.includes("raised") || + text.includes("funded") || + text.includes("achieved") || + text.includes("reached") || + text.includes("minimum") || + text.includes("optimal") || text.includes("€") || text.includes("eur") || /\d+/.test(text); if (hasFinancingContent) { const goalIndicators = [ - "conseguido", - "alcanzado", - "financiado", - "logrado", - "completado", + "achieved", + "reached", + "funded", + "accomplished", + "completed", "100%", "150%", "✓", "success", - "meta", - "objetivo", + "target", + "objective", "goal", "achieved", ]; @@ -167,7 +247,7 @@ describe("Objective achieved", () => { cy.get("body").then(($bodyAgain) => { const textAgain = $bodyAgain.text().toLowerCase(); const hasContentNow = - textAgain.includes("proyecto") || + textAgain.includes("project") || textAgain.includes("€") || /\d+/.test(textAgain); @@ -175,7 +255,7 @@ describe("Objective achieved", () => { expect(hasContentNow, "Should have some project content after waiting").to .be.true; } else { - cy.log("ℹ️ Página cargada pero con contenido limitado"); + cy.log("ℹ️ Page loaded but with limited content"); expect(true, "Page loaded without critical errors").to.be.true; } }); @@ -185,16 +265,16 @@ describe("Objective achieved", () => { cy.get("body").then(($body) => { const text = $body.text(); - if (text.includes("Obtenido") || text.includes("Recaudado")) { - cy.contains(/Obtenido|Recaudado/i).should("be.visible"); + if (text.includes("Obtained") || text.includes("Raised")) { + cy.contains(/Obtained|Raised/i).should("be.visible"); } - if (text.includes("Mínimo")) { - cy.contains("Mínimo").should("be.visible"); + if (text.includes("Minimum")) { + cy.contains("Minimum").should("be.visible"); } - if (text.includes("Óptimo")) { - cy.contains("Óptimo").should("be.visible"); + if (text.includes("Optimal")) { + cy.contains("Optimal").should("be.visible"); } if (text.includes("€")) { @@ -204,20 +284,19 @@ describe("Objective achieved", () => { }); it("should display project page with basic content", () => { - cy.visitAs("user", "/es/project/100"); + cy.visit("/es/project/100", { + failOnStatusCode: false, + timeout: 60000, + }); + + cy.get("body", { timeout: 30000 }).should("exist"); - // Wait for page to load and verify it's not a 500 error page - cy.get("body", { timeout: 20000 }).should("exist"); - - // Check if page loaded with error or if it's completely empty cy.get("body").then(($body) => { const text = $body.text(); - - // If body is completely empty or shows errors, skip test gracefully in CI + if (!text || text.trim().length === 0) { cy.log("⚠️ Empty body detected in CI - skipping test"); cy.log("This may indicate API connectivity issues in CI environment"); - // Don't fail the test, just log and continue return; } else if (text.includes("500") || text.includes("Internal Server Error")) { cy.log("❌ Server error detected"); @@ -229,18 +308,18 @@ describe("Objective achieved", () => { const text = $body.text().toLowerCase(); const contentIndicators = [ - "proyecto", "project", - "título", + "project", "title", - "descripción", + "title", + "description", "description", "€", "eur", - "obtenido", - "recaudado", - "mínimo", - "óptimo", + "obtained", + "raised", + "minimum", + "optimal", ]; let foundContent = 0; @@ -252,11 +331,11 @@ describe("Objective achieved", () => { if (foundContent >= 2 || text.length > 1000) { cy.log( - `✅ Encontrado contenido del proyecto (${foundContent} indicadores, ${text.length} chars)`, + `✅ Found project content (${foundContent} indicators, ${text.length} chars)`, ); expect(true, "Project content found").to.be.true; } else { - cy.log("ℹ️ Página básica cargada, verificando elementos mínimos"); + cy.log("ℹ️ Basic page loaded, checking minimum elements"); expect(text).to.not.include("error 500"); expect(text).to.not.include("internal server error"); diff --git a/cypress/e2e/project_progress_no_contributions.cy.ts b/cypress/e2e/project_progress_no_contributions.cy.ts index 8d06e71c..923e7e3a 100644 --- a/cypress/e2e/project_progress_no_contributions.cy.ts +++ b/cypress/e2e/project_progress_no_contributions.cy.ts @@ -2,16 +2,29 @@ describe("Viewing without contributions", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - isAuthenticated: true, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, @@ -26,6 +39,11 @@ describe("Viewing without contributions", () => { status: "active", currency: "EUR", description: "Descripción del proyecto sin contribuciones", + budget: { + minimum: { money: { amount: 5000, currency: "EUR" } }, + optimum: { money: { amount: 10000, currency: "EUR" } }, + }, + accounting: "/v4/accountings/100", owner: { id: 1, name: "Owner Test", @@ -39,6 +57,23 @@ describe("Viewing without contributions", () => { body: [], }).as("projectRewards"); + cy.intercept("GET", "**/v4/accountings/100", { + statusCode: 200, + body: { + id: 100, + balance: { + amount: 0, + currency: "EUR", + }, + accounting_balance_points: "/v4/accounting_balance_points?accounting=100", + }, + }).as("accountingData"); + + cy.intercept("GET", "**/v4/accounting_balance_points**", { + statusCode: 200, + body: [], + }).as("accountingBalancePoints"); + cy.intercept("GET", "**/v4/**", { statusCode: 200, body: { accountingId: 123, id: 1 }, @@ -71,10 +106,13 @@ describe("Viewing without contributions", () => { }); it("should display progress elements correctly", () => { - cy.visit("/es/project/100", { failOnStatusCode: false }); + cy.visit("/es/project/100", { + failOnStatusCode: false, + timeout: 60000, + }); cy.wait(3000); - cy.get("body").should("exist"); + cy.get("body", { timeout: 30000 }).should("exist"); cy.get("body").should("not.contain", "Error 500"); cy.get("body").should("not.contain", "Internal Server Error"); diff --git a/cypress/e2e/stripe_payment_flow.cy.ts b/cypress/e2e/stripe_payment_flow.cy.ts index 8cf97125..ef7092ef 100644 --- a/cypress/e2e/stripe_payment_flow.cy.ts +++ b/cypress/e2e/stripe_payment_flow.cy.ts @@ -2,30 +2,39 @@ describe("Stripe Payment Flow", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: "mock-access-token-cypress-123", - refresh_token: "mock-refresh-token-cypress-456", - user: { - id: 1, - email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - }, + id: 1, + token: "mock-access-token-cypress-123", + owner: "/v4/users/1", }, }).as("loginRequest"); + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); + cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, body: { diff --git a/cypress/e2e/test_add_multiple_contributions_to_cart.cy.ts b/cypress/e2e/test_add_multiple_contributions_to_cart.cy.ts index eb073edc..a3419e60 100644 --- a/cypress/e2e/test_add_multiple_contributions_to_cart.cy.ts +++ b/cypress/e2e/test_add_multiple_contributions_to_cart.cy.ts @@ -2,30 +2,39 @@ describe("Checkout Flow - Multiple Contributions", () => { beforeEach(() => { - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: "mock-access-token-cypress-123", - refresh_token: "mock-refresh-token-cypress-456", - user: { - id: 1, - email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - }, + id: 1, + token: "mock-access-token-cypress-123", + owner: "/v4/users/1", }, }).as("loginRequest"); + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); + cy.intercept("GET", "**/v4/projects/10", { statusCode: 200, body: { diff --git a/cypress/e2e/view_reward_details_and_amounts.cy.ts b/cypress/e2e/view_reward_details_and_amounts.cy.ts index 3bf8016f..a44afd1e 100644 --- a/cypress/e2e/view_reward_details_and_amounts.cy.ts +++ b/cypress/e2e/view_reward_details_and_amounts.cy.ts @@ -5,15 +5,29 @@ describe("View project rewards", () => { cy.loginAs("user"); cy.on("uncaught:exception", () => false); - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.intercept("GET", "**/v4/projects/100", { statusCode: 200, @@ -114,8 +128,13 @@ describe("View project rewards", () => { JSON.stringify({ id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }), ); }); @@ -124,7 +143,11 @@ describe("View project rewards", () => { "access-token", JSON.stringify({ token: "mock-access-token-cypress-123", - accountingId: 123, + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, userId: 1, }), ); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 1325f579..8d8cf379 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -8,7 +8,7 @@ // https://on.cypress.io/custom-commands // *********************************************** -// ========== INTERFACES EXISTENTES ========== +// ========== EXISTING INTERFACES ========== interface User { email: string; password: string; @@ -19,12 +19,12 @@ interface Users { invalidUser: User; } -// ========== COMANDOS ESCALABLES NUEVOS ========== -// NOTA: Los tipos UserProfile y UserRole están definidos en index.d.ts +// ========== NEW SCALABLE COMMANDS ========== +// NOTE: UserProfile and UserRole types are defined in index.d.ts /** - * Comando escalable para autenticación por rol - * Configura automáticamente intercepts, localStorage y cookies + * Scalable command for role-based authentication + * Automatically configures intercepts, localStorage and cookies */ Cypress.Commands.add("loginAs", (role: UserRole) => { cy.fixture("user-profiles").then((profiles) => { @@ -34,54 +34,77 @@ Cypress.Commands.add("loginAs", (role: UserRole) => { throw new Error(`Profile '${role}' not found in user-profiles.json`); } - // Si es guest, no configurar autenticación + // If it's guest, don't configure authentication if (role === "guest") { cy.clearCookies(); cy.clearLocalStorage(); return; } - // 1. Configurar intercept para /api/auth/me - cy.intercept("GET", "**/api/auth/me", { + // 1. Configure intercept to get user data + cy.intercept("GET", `**/v4/users/${profile.id}`, { statusCode: 200, body: { id: profile.id, email: profile.email, - name: profile.name, - accountingId: profile.accountingId, + handle: profile.email.split("@")[0], + displayName: profile.name, roles: profile.roles, - isAdmin: profile.isAdmin, + accounting: `/v4/accountings/${profile.accountingId}`, + person: `/v4/users/${profile.id}/person`, + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); - // 2. Configurar intercept para login (opcional) - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + // 2. Configure intercept for login + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: profile.token, - refresh_token: `refresh-${profile.token}`, - user: { - id: profile.id, - email: profile.email, - name: profile.name, - accountingId: profile.accountingId, - roles: profile.roles, - }, + id: 1, + token: profile.token, + owner: `/v4/users/${profile.id}`, }, }).as("loginRequest"); - // 3. Configurar cookie access-token + // 3. Configure intercept for person data + cy.intercept("GET", `**/v4/users/${profile.id}/person`, { + statusCode: 200, + body: { + id: profile.id, + name: profile.name, + email: profile.email, + }, + }).as("getPersonData"); + + // 4. Configure access-token cookie cy.setCookie( "access-token", JSON.stringify({ id: profile.id, + user: { + id: profile.id, + email: profile.email, + handle: profile.email.split("@")[0], + displayName: profile.name, + roles: profile.roles, + accounting: `/v4/accountings/${profile.accountingId}`, + person: `/v4/users/${profile.id}/person`, + emailConfirmed: true, + active: true, + }, + person: { + id: profile.id, + name: profile.name, + email: profile.email, + }, token: profile.token, accountingId: profile.accountingId, isAdmin: profile.isAdmin, }), ); - // 4. Configurar localStorage + // 5. Configure localStorage cy.window().then((win) => { win.localStorage.setItem( "user", @@ -96,16 +119,16 @@ Cypress.Commands.add("loginAs", (role: UserRole) => { ); }); - // 5. Configurar intercepts adicionales comunes + // 6. Configure additional common intercepts cy.setupCommonIntercepts(profile); - // 6. Manejar excepciones no capturadas + // 7. Handle uncaught exceptions cy.on("uncaught:exception", () => false); }); }); /** - * Configurar intercepts comunes basados en el perfil del usuario + * Configure common intercepts based on user profile */ Cypress.Commands.add("setupCommonIntercepts", (profile: UserProfile) => { cy.intercept("GET", "**/v4/projects/**", { @@ -173,7 +196,7 @@ Cypress.Commands.add("setupCommonIntercepts", (profile: UserProfile) => { }); /** - * Comando para verificar que el usuario tiene los roles correctos + * Command to verify that the user has the correct roles */ Cypress.Commands.add("verifyUserRole", (expectedRole: UserRole) => { cy.fixture("user-profiles").then((profiles) => { @@ -188,14 +211,14 @@ Cypress.Commands.add("verifyUserRole", (expectedRole: UserRole) => { }); /** - * Comando para visitar página con autenticación automática + * Command to visit page with automatic authentication */ Cypress.Commands.add("visitAs", (role: UserRole, url: string) => { cy.loginAs(role); cy.visit(url, { failOnStatusCode: false }); }); -// ========== COMANDOS EXISTENTES (MANTENER COMPATIBILIDAD) ========== +// ========== EXISTING COMMANDS (MAINTAIN COMPATIBILITY) ========== Cypress.Commands.add( "login", @@ -228,36 +251,61 @@ Cypress.Commands.add( Cypress.Commands.add("mockLogin", () => { console.warn('⚠️ cy.mockLogin() is deprecated. Use cy.loginAs("admin") instead'); - cy.intercept("POST", "**/api/auth/login", { - statusCode: 200, + cy.intercept("POST", "**/v4/user_tokens", { + statusCode: 201, body: { - access_token: "mock-access-token-cypress-123", - refresh_token: "mock-refresh-token-cypress-456", - user: { - id: 1, - email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, - }, + id: 1, + token: "mock-access-token-cypress-123", + owner: "/v4/users/1", }, }).as("loginRequest"); - cy.intercept("GET", "**/api/auth/me", { + cy.intercept("GET", "**/v4/users/1", { statusCode: 200, body: { id: 1, email: "test@cypress.local", - name: "Cypress Test User", - accountingId: 123, + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, }, - }).as("authMe"); + }).as("getUserData"); + + cy.intercept("GET", "**/v4/users/1/person", { + statusCode: 200, + body: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, + }).as("getPersonData"); cy.setCookie( "access-token", JSON.stringify({ + id: 1, + user: { + id: 1, + email: "test@cypress.local", + handle: "test", + displayName: "Cypress Test User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/123", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, + }, + person: { + id: 1, + name: "Cypress Test User", + email: "test@cypress.local", + }, token: "mock-access-token-cypress-123", accountingId: 123, - userId: 1, }), ); @@ -280,9 +328,25 @@ Cypress.Commands.add("loginBypass", () => { cy.setCookie( "access-token", JSON.stringify({ + id: 1, + user: { + id: 1, + email: "cypress@test.local", + handle: "cypress", + displayName: "Cypress Bypass User", + roles: ["ROLE_USER"], + accounting: "/v4/accountings/999", + person: "/v4/users/1/person", + emailConfirmed: true, + active: true, + }, + person: { + id: 1, + name: "Cypress Bypass User", + email: "cypress@test.local", + }, token: "cypress-bypass-token", accountingId: 999, - userId: 1, }), ); @@ -316,7 +380,7 @@ Cypress.Commands.add("checkHeaderElements", () => { } if ($body.find('header button[aria-label="Ir al checkout"]').length > 0) { - cy.get('header button[aria-label="Ir al checkout"]', { timeout: 5000 }).should( + cy.get('header button[aria-label="Go to checkout"]', { timeout: 5000 }).should( "be.visible", ); } else { @@ -328,7 +392,7 @@ Cypress.Commands.add("checkHeaderElements", () => { Cypress.Commands.add("changeLanguage", (language: string) => { cy.get("body").then(($body) => { if ($body.find("select#language-select").length > 0) { - cy.get("select#language-select", { timeout: 5000 }).select(language); + cy.get("select#language-select", { timeout: 5000 }).first().select(language); } else { cy.log("ℹ️ Language selector not found"); } diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts index c3c22e94..18165210 100644 --- a/cypress/support/index.d.ts +++ b/cypress/support/index.d.ts @@ -1,6 +1,6 @@ /// -// ========== TIPOS GLOBALES ========== +// ========== GLOBAL TYPES ========== interface UserProfile { id: number | null; email: string | null; @@ -13,14 +13,14 @@ interface UserProfile { type UserRole = "admin" | "user" | "userWithAdmin" | "moderator" | "guest"; -// ========== EXTENSIÓN DE CYPRESS ========== +// ========== CYPRESS EXTENSION ========== declare namespace Cypress { interface Chainable { - // ========== COMANDOS ESCALABLES NUEVOS ========== + // ========== NEW SCALABLE COMMANDS ========== /** - * 🚀 NUEVO: Autenticación escalable por rol - * Configura automáticamente intercepts, localStorage y cookies + * 🚀 NEW: Scalable role-based authentication + * Automatically configures intercepts, localStorage and cookies * @example cy.loginAs('admin') * @example cy.loginAs('user') * @example cy.loginAs('userWithAdmin') @@ -30,28 +30,28 @@ declare namespace Cypress { loginAs(role: UserRole): Chainable; /** - * 🚀 NUEVO: Visitar página con autenticación automática + * 🚀 NEW: Visit page with automatic authentication * @example cy.visitAs('admin', '/es/admin/charges') * @example cy.visitAs('user', '/es/project/123') */ visitAs(role: UserRole, url: string): Chainable; /** - * 🚀 NUEVO: Verificar que el usuario tiene los roles correctos + * 🚀 NEW: Verify that the user has the correct roles * @example cy.verifyUserRole('admin') */ verifyUserRole(expectedRole: UserRole): Chainable; /** - * 🚀 NUEVO: Configurar intercepts comunes automáticamente + * 🚀 NEW: Configure common intercepts automatically * @example cy.setupCommonIntercepts(profile) */ setupCommonIntercepts(profile: UserProfile): Chainable; - // ========== COMANDOS EXISTENTES ========== + // ========== EXISTING COMMANDS ========== /** - * ⚠️ DEPRECATED: Usar cy.loginAs('user') en su lugar + * ⚠️ DEPRECATED: Use cy.loginAs('user') instead * Custom command to log in to the application * @example cy.login() * @example cy.login('user@example.com', 'password123') @@ -59,7 +59,7 @@ declare namespace Cypress { login(username?: string, password?: string): Chainable; /** - * ⚠️ DEPRECATED: Usar cy.loginAs('admin') en su lugar + * ⚠️ DEPRECATED: Use cy.loginAs('admin') instead * Custom command to mock authentication (useful for CI) * @example cy.mockLogin() */ diff --git a/eslint.config.js b/eslint.config.js index a8738f27..432ad4d6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,7 @@ import importPlugin from "eslint-plugin-import"; import globals from "globals"; import tseslint from "typescript-eslint"; import cypressPlugin from "eslint-plugin-cypress"; +import unusedImportsPlugin from "eslint-plugin-unused-imports"; /** @type {import('eslint').Linter.Config[]} */ export default [ @@ -19,6 +20,8 @@ export default [ ".astro/**", ".astro/**/*", "openapi/client/**", + ".github/**", + "env.d.ts", ], }, { files: ["**/*.{js,mjs,cjs,ts}"] }, @@ -28,6 +31,7 @@ export default [ { plugins: { import: importPlugin, + "unused-imports": unusedImportsPlugin, }, rules: { "import/order": [ @@ -48,6 +52,7 @@ export default [ }, ], "@typescript-eslint/no-explicit-any": "off", + "unused-imports/no-unused-imports": "error", }, }, // Cypress configuration diff --git a/package.json b/package.json index 38d6e47e..111ebb2b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dev:worker": "npx astro build && npx wrangler dev --port 4321 --ip 0.0.0.0" }, "dependencies": { + "@astrojs/check": "^0.9.4", "@astrojs/cloudflare": "^12.6.0", "@astrojs/node": "^9.2.2", "@astrojs/svelte": "^7.1.0", @@ -38,6 +39,7 @@ "marked": "^15.0.12", "media-icons": "^1.1.5", "svelte": "^5.33.13", + "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.8", "typescript": "^5.8.3" }, @@ -51,6 +53,7 @@ "eslint": "^9.28.0", "eslint-plugin-cypress": "^5.0.1", "eslint-plugin-import": "^2.31.0", + "eslint-plugin-unused-imports": "^4.2.0", "flowbite": "^3.1.2", "flowbite-svelte": "1.5.2", "flowbite-svelte-icons": "^2.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 949d0924..7804be22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,15 +8,18 @@ importers: .: dependencies: + '@astrojs/check': + specifier: ^0.9.4 + version: 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(typescript@5.8.3) '@astrojs/cloudflare': specifier: ^12.6.0 - version: 12.6.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3))(jiti@2.4.2)(lightningcss@1.30.1) + version: 12.6.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1))(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) '@astrojs/node': specifier: ^9.2.2 - version: 9.2.2(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)) + version: 9.2.2(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1)) '@astrojs/svelte': specifier: ^7.1.0 - version: 7.1.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3))(jiti@2.4.2)(lightningcss@1.30.1)(svelte@5.33.13)(typescript@5.8.3) + version: 7.1.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1))(jiti@2.4.2)(lightningcss@1.30.1)(svelte@5.33.13)(typescript@5.8.3)(yaml@2.8.1) '@fontsource/karla': specifier: ^5.2.5 version: 5.2.5 @@ -25,13 +28,13 @@ importers: version: 0.11.0(@hey-api/openapi-ts@0.69.2(magicast@0.3.5)(typescript@5.8.3)) '@tailwindcss/vite': specifier: ^4.1.8 - version: 4.1.8(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)) + version: 4.1.8(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)) '@types/iso-3166-2': specifier: ^1.0.4 version: 1.0.4 astro: specifier: ^5.8.1 - version: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3) + version: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1) bits-ui: specifier: ^2.4.1 version: 2.4.1(@internationalized/date@3.7.0)(svelte@5.33.13) @@ -56,6 +59,9 @@ importers: svelte: specifier: ^5.33.13 version: 5.33.13 + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 tailwindcss: specifier: ^4.1.8 version: 4.1.8 @@ -90,6 +96,9 @@ importers: eslint-plugin-import: specifier: ^2.31.0 version: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-unused-imports: + specifier: ^4.2.0 + version: 4.2.0(@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)) flowbite: specifier: ^3.1.2 version: 3.1.2(rollup@4.36.0) @@ -133,6 +142,12 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@astrojs/check@0.9.4': + resolution: {integrity: sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + '@astrojs/cloudflare@12.6.0': resolution: {integrity: sha512-pQ8bokC59GEiXvyXpC4swBNoL7C/EknP+82KFzQwgR/Aeo5N1oPiAoPHgJbpPya/YF4E26WODdCQfBQDvLRfuw==} peerDependencies: @@ -147,6 +162,18 @@ packages: '@astrojs/internal-helpers@0.6.1': resolution: {integrity: sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==} + '@astrojs/language-server@2.15.4': + resolution: {integrity: sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + '@astrojs/markdown-remark@6.3.2': resolution: {integrity: sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q==} @@ -174,6 +201,9 @@ packages: '@astrojs/underscore-redirects@1.0.0': resolution: {integrity: sha512-qZxHwVnmb5FXuvRsaIGaqWgnftjCuMY+GSbaVZdBmE4j8AfgPqKPxYp8SUERyJcjpKCEmO4wD6ybuGH8A2kVRQ==} + '@astrojs/yaml2ts@0.2.2': + resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==} + '@babel/helper-string-parser@7.25.9': resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} @@ -251,6 +281,27 @@ packages: '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.0': + resolution: {integrity: sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} @@ -1098,6 +1149,32 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@volar/kit@2.4.23': + resolution: {integrity: sha512-YuUIzo9zwC2IkN7FStIcVl1YS9w5vkSFEZfPvnu0IbIMaR9WHhc9ZxvlT+91vrcSoRY469H2jwbrGqpG7m1KaQ==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/language-server@2.4.23': + resolution: {integrity: sha512-k0iO+tybMGMMyrNdWOxgFkP0XJTdbH0w+WZlM54RzJU3WZSjHEupwL30klpM7ep4FO6qyQa03h+VcGHD4Q8gEg==} + + '@volar/language-service@2.4.23': + resolution: {integrity: sha512-h5mU9DZ/6u3LCB9xomJtorNG6awBNnk9VuCioGsp6UtFiM8amvS5FcsaC3dabdL9zO0z+Gq9vIEMb/5u9K6jGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + '@yr/monotone-cubic-spline@1.0.3': resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==} @@ -1127,6 +1204,9 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -1410,6 +1490,10 @@ packages: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone@2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} @@ -1640,6 +1724,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -1707,6 +1794,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -1761,6 +1852,15 @@ packages: '@typescript-eslint/parser': optional: true + eslint-plugin-unused-imports@4.2.0: + resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + eslint-scope@8.3.0: resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1875,6 +1975,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -2002,6 +2105,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.3.0: resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} engines: {node: '>=18'} @@ -2397,6 +2504,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -2410,6 +2520,12 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -2788,6 +2904,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2941,6 +3060,9 @@ packages: pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3068,6 +3190,11 @@ packages: prettier-plugin-svelte: optional: true + prettier@2.8.7: + resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} + engines: {node: '>=10.13.0'} + hasBin: true + prettier@3.5.3: resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} @@ -3179,9 +3306,23 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + request-progress@3.0.0: resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3462,8 +3603,8 @@ packages: tailwind-merge@3.0.2: resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==} - tailwind-merge@3.3.0: - resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} tailwind-variants@1.0.0: resolution: {integrity: sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==} @@ -3594,6 +3735,12 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + typescript-eslint@8.33.1: resolution: {integrity: sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3814,6 +3961,112 @@ packages: vite: optional: true + volar-service-css@0.0.62: + resolution: {integrity: sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.62: + resolution: {integrity: sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.62: + resolution: {integrity: sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.62: + resolution: {integrity: sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.62: + resolution: {integrity: sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.62: + resolution: {integrity: sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.62: + resolution: {integrity: sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.8: + resolution: {integrity: sha512-dBk/9ullEjIMbfSYAohGpDOisOVU1x2MQHOeU12ohGJQI7+r0PCimBwaa/pWpxl/vH4f7ibrBfxIZY3anGmHKQ==} + + vscode-html-languageservice@5.5.2: + resolution: {integrity: sha512-QpaUhCjvb7U/qThOzo4V6grwsRE62Jk/vf8BRJZoABlMw3oplLB5uovrvcrLO9vYhkeMiSjyqLnCxbfHzzZqmw==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@6.0.0: + resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} + engines: {node: '>=8.0.0 || >=10.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.16.0: + resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.16.0: + resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@7.0.0: + resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + hasBin: true + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + wait-on@8.0.3: resolution: {integrity: sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==} engines: {node: '>=12.0.0'} @@ -3914,6 +4167,10 @@ packages: xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -3921,10 +4178,27 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-language-server@1.15.0: + resolution: {integrity: sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==} + hasBin: true + + yaml@2.2.2: + resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} + engines: {node: '>= 14'} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} @@ -3980,14 +4254,25 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@astrojs/cloudflare@12.6.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3))(jiti@2.4.2)(lightningcss@1.30.1)': + '@astrojs/check@0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(typescript@5.8.3)': + dependencies: + '@astrojs/language-server': 2.15.4(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(typescript@5.8.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 5.8.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + + '@astrojs/cloudflare@12.6.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1))(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)': dependencies: '@astrojs/internal-helpers': 0.6.1 '@astrojs/underscore-redirects': 1.0.0 '@cloudflare/workers-types': 4.20250620.0 - astro: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3) + astro: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1) tinyglobby: 0.2.14 - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) wrangler: 4.37.1(@cloudflare/workers-types@4.20250620.0) transitivePeerDependencies: - '@types/node' @@ -4010,6 +4295,32 @@ snapshots: '@astrojs/internal-helpers@0.6.1': {} + '@astrojs/language-server@2.15.4(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(typescript@5.8.3)': + dependencies: + '@astrojs/compiler': 2.12.2 + '@astrojs/yaml2ts': 0.2.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.23(typescript@5.8.3) + '@volar/language-core': 2.4.23 + '@volar/language-server': 2.4.23 + '@volar/language-service': 2.4.23 + fast-glob: 3.3.3 + muggle-string: 0.4.1 + volar-service-css: 0.0.62(@volar/language-service@2.4.23) + volar-service-emmet: 0.0.62(@volar/language-service@2.4.23) + volar-service-html: 0.0.62(@volar/language-service@2.4.23) + volar-service-prettier: 0.0.62(@volar/language-service@2.4.23)(prettier@3.5.3) + volar-service-typescript: 0.0.62(@volar/language-service@2.4.23) + volar-service-typescript-twoslash-queries: 0.0.62(@volar/language-service@2.4.23) + volar-service-yaml: 0.0.62(@volar/language-service@2.4.23) + vscode-html-languageservice: 5.5.2 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.5.3 + prettier-plugin-astro: 0.14.1 + transitivePeerDependencies: + - typescript + '@astrojs/markdown-remark@6.3.2': dependencies: '@astrojs/internal-helpers': 0.6.1 @@ -4036,10 +4347,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/node@9.2.2(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3))': + '@astrojs/node@9.2.2(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1))': dependencies: '@astrojs/internal-helpers': 0.6.1 - astro: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3) + astro: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1) send: 1.2.0 server-destroy: 1.0.1 transitivePeerDependencies: @@ -4049,14 +4360,14 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/svelte@7.1.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3))(jiti@2.4.2)(lightningcss@1.30.1)(svelte@5.33.13)(typescript@5.8.3)': + '@astrojs/svelte@7.1.0(@types/node@24.0.4)(astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1))(jiti@2.4.2)(lightningcss@1.30.1)(svelte@5.33.13)(typescript@5.8.3)(yaml@2.8.1)': dependencies: - '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)) - astro: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)) + astro: 5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1) svelte: 5.33.13 svelte2tsx: 0.7.39(svelte@5.33.13)(typescript@5.8.3) typescript: 5.8.3 - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -4085,6 +4396,10 @@ snapshots: '@astrojs/underscore-redirects@1.0.0': {} + '@astrojs/yaml2ts@0.2.2': + dependencies: + yaml: 2.8.1 + '@babel/helper-string-parser@7.25.9': {} '@babel/helper-validator-identifier@7.25.9': {} @@ -4165,6 +4480,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.0': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + '@emnapi/runtime@1.3.1': dependencies: tslib: 2.8.1 @@ -4632,25 +4970,25 @@ snapshots: dependencies: acorn: 8.14.1 - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)) debug: 4.4.1(supports-color@8.1.1) svelte: 5.33.13 - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.33.13)(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)) debug: 4.4.1(supports-color@8.1.1) deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.33.13 - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) - vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) + vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)) transitivePeerDependencies: - supports-color @@ -4745,12 +5083,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.8 '@tailwindcss/oxide-win32-x64-msvc': 4.1.8 - '@tailwindcss/vite@4.1.8(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.8(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.1.8 '@tailwindcss/oxide': 4.1.8 tailwindcss: 4.1.8 - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) '@types/debug@4.1.12': dependencies: @@ -4893,6 +5231,56 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@volar/kit@2.4.23(typescript@5.8.3)': + dependencies: + '@volar/language-service': 2.4.23 + '@volar/typescript': 2.4.23 + typesafe-path: 0.2.2 + typescript: 5.8.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/language-server@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + '@volar/language-service': 2.4.23 + '@volar/typescript': 2.4.23 + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + '@yr/monotone-cubic-spline@1.0.3': {} acorn-jsx@5.3.2(acorn@8.14.1): @@ -4917,6 +5305,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -5017,7 +5412,7 @@ snapshots: astral-regex@2.0.0: {} - astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3): + astro@5.10.1(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.36.0)(typescript@5.8.3)(yaml@2.8.1): dependencies: '@astrojs/compiler': 2.12.2 '@astrojs/internal-helpers': 0.6.1 @@ -5072,8 +5467,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.15.0 vfile: 6.0.3 - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) - vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) + vitefu: 1.0.6(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.1 @@ -5314,6 +5709,12 @@ snapshots: slice-ansi: 3.0.0 string-width: 4.2.3 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone@2.1.2: {} clsx@2.1.1: {} @@ -5549,6 +5950,11 @@ snapshots: ee-first@1.1.1: {} + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + emoji-regex-xs@1.0.0: {} emoji-regex@10.4.0: {} @@ -5684,6 +6090,8 @@ snapshots: '@esbuild/win32-ia32': 0.25.4 '@esbuild/win32-x64': 0.25.4 + escalade@3.2.0: {} + escape-html@1.0.3: {} escape-string-regexp@1.0.5: {} @@ -5744,6 +6152,12 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)): + dependencies: + eslint: 9.28.0(jiti@2.4.2) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 @@ -5903,6 +6317,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -5962,7 +6378,7 @@ snapshots: dependencies: clsx: 2.1.1 svelte: 5.33.13 - tailwind-merge: 3.3.0 + tailwind-merge: 3.3.1 flowbite-svelte@1.5.2(rollup@4.36.0)(svelte@5.33.13)(tailwindcss@4.1.8): dependencies: @@ -5972,7 +6388,7 @@ snapshots: clsx: 2.1.1 flowbite: 3.1.2(rollup@4.36.0) svelte: 5.33.13 - tailwind-merge: 3.3.0 + tailwind-merge: 3.3.1 tailwind-variants: 1.0.0(tailwindcss@4.1.8) tailwindcss: 4.1.8 transitivePeerDependencies: @@ -6068,6 +6484,8 @@ snapshots: functions-have-names@1.2.3: {} + get-caller-file@2.0.5: {} + get-east-asian-width@1.3.0: {} get-intrinsic@1.3.0: @@ -6518,6 +6936,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -6528,6 +6948,10 @@ snapshots: dependencies: minimist: 1.2.8 + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -7066,6 +7490,8 @@ snapshots: ms@2.1.3: {} + muggle-string@0.4.1: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -7236,6 +7662,8 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -7300,6 +7728,9 @@ snapshots: prettier-plugin-astro: 0.14.1 prettier-plugin-svelte: 3.4.0(prettier@3.5.3)(svelte@5.33.13) + prettier@2.8.7: + optional: true + prettier@3.5.3: {} pretty-bytes@5.6.0: {} @@ -7445,10 +7876,18 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + request-light@0.5.8: {} + + request-light@0.7.0: {} + request-progress@3.0.0: dependencies: throttleit: 1.0.1 + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve@1.22.10: @@ -7851,7 +8290,7 @@ snapshots: tailwind-merge@3.0.2: {} - tailwind-merge@3.3.0: {} + tailwind-merge@3.3.1: {} tailwind-variants@1.0.0(tailwindcss@4.1.8): dependencies: @@ -7983,6 +8422,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.7.1 + typescript-eslint@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): dependencies: '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) @@ -8138,7 +8583,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1): + vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1): dependencies: esbuild: 0.25.4 fdir: 6.4.5(picomatch@4.0.2) @@ -8151,10 +8596,121 @@ snapshots: fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 + yaml: 2.8.1 - vitefu@1.0.6(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)): + vitefu@1.0.6(vite@6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1)): optionalDependencies: - vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1) + vite: 6.3.5(@types/node@24.0.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.1) + + volar-service-css@0.0.62(@volar/language-service@2.4.23): + dependencies: + vscode-css-languageservice: 6.3.8 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + + volar-service-emmet@0.0.62(@volar/language-service@2.4.23): + dependencies: + '@emmetio/css-parser': 0.4.0 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + + volar-service-html@0.0.62(@volar/language-service@2.4.23): + dependencies: + vscode-html-languageservice: 5.5.2 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + + volar-service-prettier@0.0.62(@volar/language-service@2.4.23)(prettier@3.5.3): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + prettier: 3.5.3 + + volar-service-typescript-twoslash-queries@0.0.62(@volar/language-service@2.4.23): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + + volar-service-typescript@0.0.62(@volar/language-service@2.4.23): + dependencies: + path-browserify: 1.0.1 + semver: 7.7.1 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + + volar-service-yaml@0.0.62(@volar/language-service@2.4.23): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.15.0 + optionalDependencies: + '@volar/language-service': 2.4.23 + + vscode-css-languageservice@6.3.8: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.5.2: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@6.0.0: {} + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.16.0: + dependencies: + vscode-jsonrpc: 6.0.0 + vscode-languageserver-types: 3.16.0 + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.16.0: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@7.0.0: + dependencies: + vscode-languageserver-protocol: 3.16.0 + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} wait-on@8.0.3(debug@4.4.1): dependencies: @@ -8289,12 +8845,43 @@ snapshots: xxhash-wasm@1.1.0: {} + y18n@5.0.8: {} + yallist@4.0.0: {} yallist@5.0.0: {} + yaml-language-server@1.15.0: + dependencies: + ajv: 8.17.1 + lodash: 4.17.21 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 7.0.0 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + yaml: 2.2.2 + optionalDependencies: + prettier: 2.8.7 + + yaml@2.2.2: {} + + yaml@2.8.1: {} + yargs-parser@21.1.1: {} + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yauzl@2.10.0: dependencies: buffer-crc32: 0.2.13 diff --git a/public/images/profile/ilustration-donations.png b/public/images/profile/ilustration-donations.png new file mode 100644 index 00000000..38ee0cba Binary files /dev/null and b/public/images/profile/ilustration-donations.png differ diff --git a/public/images/profile/ilustration-matchfunding.png b/public/images/profile/ilustration-matchfunding.png new file mode 100644 index 00000000..403145af Binary files /dev/null and b/public/images/profile/ilustration-matchfunding.png differ diff --git a/public/images/profile/ilustration-project.png b/public/images/profile/ilustration-project.png new file mode 100644 index 00000000..8c27e6ad Binary files /dev/null and b/public/images/profile/ilustration-project.png differ diff --git a/public/imgs/banner-home.svg b/public/imgs/banner-home.svg new file mode 100644 index 00000000..9467247f --- /dev/null +++ b/public/imgs/banner-home.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/path2.svg b/public/path2.svg new file mode 100644 index 00000000..b292b633 --- /dev/null +++ b/public/path2.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/post-payment.svg b/public/post-payment.svg new file mode 100644 index 00000000..059c5515 --- /dev/null +++ b/public/post-payment.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/actions/payment.ts b/src/actions/payment.ts index 37f3b8ec..e1d5fd0e 100644 --- a/src/actions/payment.ts +++ b/src/actions/payment.ts @@ -1,35 +1,12 @@ import { z } from "astro/zod"; import { ActionError, defineAction } from "astro:actions"; -import { apiGatewayCheckoutsPost, apiGatewaysGetCollection } from "../openapi/client/index.ts"; +import { apiGatewayCheckoutsPost } from "../openapi/client/index.ts"; import { getDefaultCurrency } from "../utils/consts.ts"; -import type { GatewayCharge, ApiGatewaysNameGetResponse } from "../openapi/client/index.ts"; +import type { GatewayCharge } from "../openapi/client/index.ts"; const defaultCurrency = getDefaultCurrency(); -async function getPaymentGateways(): Promise { - if (import.meta.env.NODE_ENV === "test" || process.env.NODE_ENV === "test") { - return [{ name: "test-gateway" }] as ApiGatewaysNameGetResponse[]; - } - - try { - const response = await apiGatewaysGetCollection(); - const paymentGateways: ApiGatewaysNameGetResponse[] | undefined = response.data; - - if (!paymentGateways) { - throw new Error("Payment gateways not found"); - } - - return paymentGateways; - } catch (error) { - console.error("Error fetching payment gateways:", error); - // En caso de error, devolver un gateway por defecto o re-lanzar según necesites - throw error; - } -} - -let cachedPaymentGateways: ApiGatewaysNameGetResponse[] | null = null; -let cachedPaymentGatewayNames: string[] | null = null; export const payment = defineAction({ accept: "form", @@ -61,23 +38,6 @@ export const payment = defineAction({ const { t } = context.locals; try { - if (!cachedPaymentGateways) { - cachedPaymentGateways = await getPaymentGateways(); - cachedPaymentGatewayNames = cachedPaymentGateways - .map((gateway) => gateway.name) - .filter((name): name is string => name !== undefined); - } - - if ( - cachedPaymentGatewayNames === null || - !cachedPaymentGatewayNames.includes(input.paymentMethod) - ) { - throw new ActionError({ - code: "BAD_REQUEST", - message: t("payment.error.invalidPaymentMethod"), - }); - } - const cart = input.cartData; const charges: GatewayCharge[] = cart.items.map((item) => ({ type: "single", @@ -115,8 +75,6 @@ export const payment = defineAction({ }, }); - console.log({ response }); - return { success: true, checkout: response.data }; } catch (err) { console.error(err); diff --git a/src/components/Admin/ActionsBtn.svelte b/src/components/Admin/ActionsBtn.svelte index 9b24f7e6..e5932187 100644 --- a/src/components/Admin/ActionsBtn.svelte +++ b/src/components/Admin/ActionsBtn.svelte @@ -5,21 +5,21 @@
diff --git a/src/components/Admin/ContentFooter.svelte b/src/components/Admin/ContentFooter.svelte index db4427a4..03c0c8f8 100644 --- a/src/components/Admin/ContentFooter.svelte +++ b/src/components/Admin/ContentFooter.svelte @@ -44,11 +44,11 @@ {/if}
-

+

{$t("contributions.grid.content-footer.downloadDetails")}

{$t("contributions.grid.content-footer.annotations", { diff --git a/src/components/Admin/DetailsRow.svelte b/src/components/Admin/DetailsRow.svelte index 2d93fef2..111aa68b 100644 --- a/src/components/Admin/DetailsRow.svelte +++ b/src/components/Admin/DetailsRow.svelte @@ -59,7 +59,7 @@

{$t("contributions.grid.details.operationTime")}

@@ -68,7 +68,7 @@

{$t("contributions.grid.details.trackingCodes.title")}

@@ -322,7 +322,7 @@ type="button" onclick={handleExportCSV} disabled={isExporting} - class="relative inline-flex cursor-pointer items-center gap-2 rounded-2xl bg-[#E6E5F7] px-4 py-2 font-medium text-nowrap text-gray-700 transition-colors disabled:cursor-not-allowed disabled:opacity-50" + class="bg-variant1 relative inline-flex cursor-pointer items-center gap-2 rounded-2xl px-4 py-2 font-medium text-nowrap text-gray-700 transition-colors disabled:cursor-not-allowed disabled:opacity-50" > {#if isExporting} diff --git a/src/components/Admin/Pagination.svelte b/src/components/Admin/Pagination.svelte index 7da21e92..0e9a4714 100644 --- a/src/components/Admin/Pagination.svelte +++ b/src/components/Admin/Pagination.svelte @@ -39,26 +39,26 @@ … + … {:else} {#if total > 0} - + {@html $t("contributions.pagination.showing", { items, total }, { allowHTML: true })} {:else if total === 0 && !isLoading} - + {@html $t("contributions.pagination.noResults")} {:else} - + {@html $t("contributions.pagination.unloaded")} {/if} diff --git a/src/components/Admin/Search.svelte b/src/components/Admin/Search.svelte index 7c17624a..70aedc42 100644 --- a/src/components/Admin/Search.svelte +++ b/src/components/Admin/Search.svelte @@ -100,7 +100,7 @@ oninput={(e) => handleInput(e.target instanceof HTMLInputElement ? e.target.value : "")} placeholder={$t("contributions.filters.search.placeholder")} - class="border-tertiary w-full rounded-3xl border p-4" + class="border-secondary w-full rounded-3xl border p-4" minlength="4" /> {#if query} diff --git a/src/components/Admin/Slider.svelte b/src/components/Admin/Slider.svelte index 30b64e27..167d10de 100644 --- a/src/components/Admin/Slider.svelte +++ b/src/components/Admin/Slider.svelte @@ -44,17 +44,17 @@
{#if !isLoaded}
- Cargando... + Cargando...
{/if} diff --git a/src/components/Admin/Table.svelte b/src/components/Admin/Table.svelte index 0a363aab..1f7ca506 100644 --- a/src/components/Admin/Table.svelte +++ b/src/components/Admin/Table.svelte @@ -507,12 +507,12 @@
-

+

{$t("contributions.filters.order.title")}

@@ -540,10 +540,10 @@
- + {#each tableHeaders as header} handleHeaderClick(header)} > @@ -579,44 +579,44 @@ toggleRow(i)} class="{openRow === i - ? 'bg-[#FAF9FF]' - : 'bg-white'} border border-[#e6e5f7] transition-colors hover:bg-[#FAF9FF]" + ? 'bg-soft-purple]' + : 'bg-white'} border-variant1 hover:bg-soft-purple] border transition-colors" > {charge.targetDisplayName} {#if charge.money.amount && charge.money.currency} - + {formatCurrency(charge.money.amount, charge.money.currency)} {:else} - — + — {/if} - {charge.originDisplayName} - + {$t(`contributions.table.rows.payments.${charge.paymentMethod}`)} - + {getDate(charge.dateCreated).date}

{charge.trackingCodes[0]?.value || "—"}

- + - {charge.refundToWallet}
@@ -624,7 +624,7 @@ + import CollapsibleBox from "./CollapsibleBox.svelte"; + import { t } from "../i18n/store"; + + +
+ + {#snippet header()} +

+ {$t("login.page.welcome.title")} +

+ {/snippet} +
+
+ + diff --git a/src/components/BackButton.astro b/src/components/BackButton.astro index f067478c..e1c57128 100644 --- a/src/components/BackButton.astro +++ b/src/components/BackButton.astro @@ -4,7 +4,7 @@ const { t } = Astro.locals;
- {item.quantity}
@@ -65,13 +74,6 @@ - - @@ -81,7 +83,7 @@ id={`reward-checkbox-${item.key}`} class="accent-primary h-6 w-6 rounded" /> -