diff --git a/next.config.mjs b/next.config.mjs index 4678774..21a6b26 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,12 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + remotePatterns: [ + { + hostname: "images.unsplash.com", + }, + ], + }, +}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 9d86004..d743d44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,15 @@ "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.2", - "@requestnetwork/payment-widget": "^0.3.1", + "@requestnetwork/payment-widget": "^0.3.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^4.1.0", + "embla-carousel-autoplay": "^8.3.1", + "embla-carousel-react": "^8.3.1", "framer-motion": "^11.3.28", "lucide-react": "^0.428.0", "next": "14.2.5", @@ -30,7 +34,8 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "validator": "^13.12.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zustand": "^5.0.1" }, "devDependencies": { "@types/node": "^20", @@ -2091,6 +2096,7 @@ }, "node_modules/@parcel/watcher-wasm/node_modules/napi-wasm": { "version": "1.1.0", + "extraneous": true, "inBundle": true, "license": "MIT" }, @@ -2591,6 +2597,36 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -2636,6 +2672,72 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", + "integrity": "sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", @@ -2807,13 +2909,13 @@ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" }, "node_modules/@requestnetwork/advanced-logic": { - "version": "0.44.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/advanced-logic/-/advanced-logic-0.44.1-next.2088.tgz", - "integrity": "sha512-scwdrhtsqJEVv5cwzf2swI1gWyu1piA9X8aJtBrWp9UeVoJq68v5pP/Ya5j0bWtys98x1Vg15rXIyUkZAtBCFQ==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@requestnetwork/advanced-logic/-/advanced-logic-0.45.0.tgz", + "integrity": "sha512-CgEahYeYlRHU5OJZh4OC/INBulQERN1d/hA7XIKut0iBZ5APl4bMypGPkoBzp7450KlfSH5q8WVJl0Vn5a7poA==", "dependencies": { - "@requestnetwork/currency": "0.18.1-next.2088+9af0ebdc", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/currency": "0.19.0", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "tslib": "2.5.0" }, "engines": { @@ -2821,13 +2923,13 @@ } }, "node_modules/@requestnetwork/advanced-logic/node_modules/@requestnetwork/currency": { - "version": "0.18.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/currency/-/currency-0.18.1-next.2088.tgz", - "integrity": "sha512-cUBAKY5UIqpye5BHbWBb+VE16USmkm2BFT18wn/ymGPNKrfYuGFPMGdJuCNDbOOjNIV4NaO9QoAyRzDLlnukYg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@requestnetwork/currency/-/currency-0.19.0.tgz", + "integrity": "sha512-TAgV/045bX8YN6GejbEArPjNdSCjwaK3MvuoHidla2gViJup8oqLb5YOicVQ87NUfNYh5FQcIXTgW6m0M/bP/Q==", "dependencies": { "@metamask/contract-metadata": "1.31.0", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "multicoin-address-validator": "0.5.15", "node-dijkstra": "2.5.0", "tslib": "2.5.0" @@ -2837,9 +2939,9 @@ } }, "node_modules/@requestnetwork/advanced-logic/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -2848,11 +2950,11 @@ } }, "node_modules/@requestnetwork/advanced-logic/node_modules/@requestnetwork/utils": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1-next.2088.tgz", - "integrity": "sha512-xTG362M7WaAWFiWpIGJpZnLWhfvSy5Z0jwp0jfK4/sGB14n0oEgbuItYDl/iev3j3e/galm5DiCoKg4N5JhbqQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1.tgz", + "integrity": "sha512-qr4K6sPkwMFbQpMtMRv1Fsuzc3xXYyVmGzVdTBW/6NcFhEKfEq0Cz5kptxCeu3IZ83+FrZVsNi/5gyu428Bffw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "@toruslabs/eccrypto": "4.0.0", "ethers": "5.7.2", "secp256k1": "4.0.4", @@ -2898,13 +3000,13 @@ } }, "node_modules/@requestnetwork/data-access": { - "version": "0.36.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/data-access/-/data-access-0.36.1-next.2088.tgz", - "integrity": "sha512-GwsuMiKRkAYrSSh3POEirLBeAwItvRIHt1F6/BEa/kg4kavGyl3tSWpzYHO/AzcvRhoX5kWIOtslypPtbsxNig==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/data-access/-/data-access-0.36.1.tgz", + "integrity": "sha512-SBe/ttquaxaAuvgzDdFnttMCJem/zWHMre8iaeY4UQmkOWR3YwpK25BIQXfr6/H7UqrTBE0/4BGeq35em6eDRQ==", "dependencies": { - "@requestnetwork/multi-format": "0.19.1-next.2088+9af0ebdc", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/multi-format": "0.19.1", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "tslib": "2.5.0" }, "engines": { @@ -2912,9 +3014,9 @@ } }, "node_modules/@requestnetwork/data-access/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -2923,11 +3025,11 @@ } }, "node_modules/@requestnetwork/data-access/node_modules/@requestnetwork/utils": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1-next.2088.tgz", - "integrity": "sha512-xTG362M7WaAWFiWpIGJpZnLWhfvSy5Z0jwp0jfK4/sGB14n0oEgbuItYDl/iev3j3e/galm5DiCoKg4N5JhbqQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1.tgz", + "integrity": "sha512-qr4K6sPkwMFbQpMtMRv1Fsuzc3xXYyVmGzVdTBW/6NcFhEKfEq0Cz5kptxCeu3IZ83+FrZVsNi/5gyu428Bffw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "@toruslabs/eccrypto": "4.0.0", "ethers": "5.7.2", "secp256k1": "4.0.4", @@ -2957,9 +3059,9 @@ } }, "node_modules/@requestnetwork/data-format": { - "version": "0.19.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/data-format/-/data-format-0.19.1-next.2088.tgz", - "integrity": "sha512-l53AtAqqTf65I/qgHWEXnjJ+u0sUKELT0s16yKTvzKrHi+OFiMrLBLkT9iO6ylLT7kStwqhV5+JZfLkdyFr8CA==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/data-format/-/data-format-0.19.1.tgz", + "integrity": "sha512-jI4w55cOmSgBmG1fU89yrFRym/C+YE2OYQ8yXieWNjaYU6d6ukP96EE82Un6g+Jc/edkuy/7PD0nfK0ag0OSKQ==", "dependencies": { "ajv": "6.12.4", "ethers": "5.7.2", @@ -2970,12 +3072,12 @@ } }, "node_modules/@requestnetwork/epk-signature": { - "version": "0.9.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/epk-signature/-/epk-signature-0.9.1-next.2088.tgz", - "integrity": "sha512-d37PFle840G2Lx/Tbq0UHoDsxubj8Yg1khKi+lxVDgG7BPJSP114M2M0yBQUHLq6jt1O8l/Am1xybyOU4HoVzg==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/epk-signature/-/epk-signature-0.9.1.tgz", + "integrity": "sha512-vxuPb4ALCAtoh9wzn6mmP6lEa174TcK05InuDwheSfuVy8xCttGRqXJnH1ybuTDOW1nQaUuz/EsGb4KhUL1uXw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "tslib": "2.5.0" }, "engines": { @@ -2983,9 +3085,9 @@ } }, "node_modules/@requestnetwork/epk-signature/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -2994,11 +3096,11 @@ } }, "node_modules/@requestnetwork/epk-signature/node_modules/@requestnetwork/utils": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1-next.2088.tgz", - "integrity": "sha512-xTG362M7WaAWFiWpIGJpZnLWhfvSy5Z0jwp0jfK4/sGB14n0oEgbuItYDl/iev3j3e/galm5DiCoKg4N5JhbqQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1.tgz", + "integrity": "sha512-qr4K6sPkwMFbQpMtMRv1Fsuzc3xXYyVmGzVdTBW/6NcFhEKfEq0Cz5kptxCeu3IZ83+FrZVsNi/5gyu428Bffw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "@toruslabs/eccrypto": "4.0.0", "ethers": "5.7.2", "secp256k1": "4.0.4", @@ -3028,11 +3130,11 @@ } }, "node_modules/@requestnetwork/multi-format": { - "version": "0.19.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/multi-format/-/multi-format-0.19.1-next.2088.tgz", - "integrity": "sha512-iWWlEM0F3KvL0vJ9l1jMHIhKs+0oJ8q9gwVm79weuhefYWkD3DpCYRc/yxB98U0w3Ab8KDAPA9+8rNbPzDsPfw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/multi-format/-/multi-format-0.19.1.tgz", + "integrity": "sha512-XRwpXSY3Lv7gKP1mi8UutpNzMCZGhS391CYGoKBcj7x7qsXOrJv5MxgEphTyCJXHbVEM79t3mtFtxUgfikb4IA==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "tslib": "2.5.0" }, "engines": { @@ -3040,9 +3142,9 @@ } }, "node_modules/@requestnetwork/multi-format/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -4609,12 +4711,12 @@ } }, "node_modules/@requestnetwork/payment-widget": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@requestnetwork/payment-widget/-/payment-widget-0.3.1.tgz", - "integrity": "sha512-xLbmuArxCPQ92OF6EKmBoxrkcQyvY7btCew2n0YaoN15bi9/pRBUyBv5D8tvhiJReK3dJK9XVqKI028H86+8WA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@requestnetwork/payment-widget/-/payment-widget-0.3.2.tgz", + "integrity": "sha512-XPgL0KRmapndKmGG/ZIke5q5KMtHwbsSDziVrct0Cov/Wdu0mhGwA3skAX8j0Oxm+FyS+7QrjdGPP38o/lGyNw==", "dependencies": { "@requestnetwork/payment-processor": "^0.47.0", - "@requestnetwork/request-client.js": "^0.49.1-next.2088", + "@requestnetwork/request-client.js": "^0.50.0", "@requestnetwork/web3-signature": "^0.8.0", "@web3modal/ethers5": "^5.0.11", "ethers": "^5.7.2", @@ -4626,22 +4728,22 @@ } }, "node_modules/@requestnetwork/request-client.js": { - "version": "0.49.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/request-client.js/-/request-client.js-0.49.1-next.2088.tgz", - "integrity": "sha512-RuXat8wbXLAMME1h2fljEYqq4WXd2vk88U3Utjj06am810CkGoyCtvDaAWrvmGJky+oW9B71d8WZ1B0mjGlVrQ==", - "dependencies": { - "@requestnetwork/advanced-logic": "0.44.1-next.2088+9af0ebdc", - "@requestnetwork/currency": "0.18.1-next.2088+9af0ebdc", - "@requestnetwork/data-access": "0.36.1-next.2088+9af0ebdc", - "@requestnetwork/data-format": "0.19.1-next.2088+9af0ebdc", - "@requestnetwork/epk-signature": "0.9.1-next.2088+9af0ebdc", - "@requestnetwork/multi-format": "0.19.1-next.2088+9af0ebdc", - "@requestnetwork/payment-detection": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/request-logic": "0.35.1-next.2088+9af0ebdc", - "@requestnetwork/smart-contracts": "0.38.1-next.2088+9af0ebdc", - "@requestnetwork/transaction-manager": "0.36.1-next.2088+9af0ebdc", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@requestnetwork/request-client.js/-/request-client.js-0.50.0.tgz", + "integrity": "sha512-jOxRhUUzXD+ffgzt5LXCzVQeYW0ow3P6Vf+ZVPAh9Z/4iaEjIMfWkqoLZTe9mXQnx+0BfKUwyDYzyk5z9BDkyQ==", + "dependencies": { + "@requestnetwork/advanced-logic": "0.45.0", + "@requestnetwork/currency": "0.19.0", + "@requestnetwork/data-access": "0.36.1", + "@requestnetwork/data-format": "0.19.1", + "@requestnetwork/epk-signature": "0.9.1", + "@requestnetwork/multi-format": "0.19.1", + "@requestnetwork/payment-detection": "0.45.1", + "@requestnetwork/request-logic": "0.35.1", + "@requestnetwork/smart-contracts": "0.39.0", + "@requestnetwork/transaction-manager": "0.36.1", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "ethers": "5.7.2", "qs": "6.11.2", "tslib": "2.5.0" @@ -4651,13 +4753,13 @@ } }, "node_modules/@requestnetwork/request-client.js/node_modules/@requestnetwork/currency": { - "version": "0.18.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/currency/-/currency-0.18.1-next.2088.tgz", - "integrity": "sha512-cUBAKY5UIqpye5BHbWBb+VE16USmkm2BFT18wn/ymGPNKrfYuGFPMGdJuCNDbOOjNIV4NaO9QoAyRzDLlnukYg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@requestnetwork/currency/-/currency-0.19.0.tgz", + "integrity": "sha512-TAgV/045bX8YN6GejbEArPjNdSCjwaK3MvuoHidla2gViJup8oqLb5YOicVQ87NUfNYh5FQcIXTgW6m0M/bP/Q==", "dependencies": { "@metamask/contract-metadata": "1.31.0", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "multicoin-address-validator": "0.5.15", "node-dijkstra": "2.5.0", "tslib": "2.5.0" @@ -4667,14 +4769,14 @@ } }, "node_modules/@requestnetwork/request-client.js/node_modules/@requestnetwork/payment-detection": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/payment-detection/-/payment-detection-0.45.1-next.2088.tgz", - "integrity": "sha512-dJhziM21zwmf5MIG06wdtpYDTdnOqmTCKrXNEgQ3Sjrh4R8PNuGU8tQ7zqVno9mzVU8WZQe0sfplLIlrMgmqgA==", - "dependencies": { - "@requestnetwork/currency": "0.18.1-next.2088+9af0ebdc", - "@requestnetwork/smart-contracts": "0.38.1-next.2088+9af0ebdc", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/payment-detection/-/payment-detection-0.45.1.tgz", + "integrity": "sha512-6t9uNJXBP8cUJ+8vVJ6cBlUuWjxqkiAyFhZ/Jg0KTqSFyoP1nj9Wn+SVK+hIvQfTwxybSn95gHIAXCIYIvIxnQ==", + "dependencies": { + "@requestnetwork/currency": "0.19.0", + "@requestnetwork/smart-contracts": "0.39.0", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "ethers": "5.7.2", "graphql": "16.8.1", "graphql-request": "6.1.0", @@ -4687,9 +4789,9 @@ } }, "node_modules/@requestnetwork/request-client.js/node_modules/@requestnetwork/smart-contracts": { - "version": "0.38.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/smart-contracts/-/smart-contracts-0.38.1-next.2088.tgz", - "integrity": "sha512-Re2PDVLnOQzlpXrgddV/GXpeqtQxYhL2t27V0oejH56r7a1m/LuZ36DmQHqx4ZLw3lw6vx93TiEZmxb0iHq1fA==", + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@requestnetwork/smart-contracts/-/smart-contracts-0.39.0.tgz", + "integrity": "sha512-wxZ38EjjdTig9F2MBTAh5nE1wa50xBGLMiy4EYx6wpTxAQdiwc7s5RWmgULxrAY13huGSBjo6zt8qzIQLuydhA==", "dependencies": { "tslib": "2.5.0" }, @@ -4698,9 +4800,9 @@ } }, "node_modules/@requestnetwork/request-client.js/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -4709,11 +4811,11 @@ } }, "node_modules/@requestnetwork/request-client.js/node_modules/@requestnetwork/utils": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1-next.2088.tgz", - "integrity": "sha512-xTG362M7WaAWFiWpIGJpZnLWhfvSy5Z0jwp0jfK4/sGB14n0oEgbuItYDl/iev3j3e/galm5DiCoKg4N5JhbqQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1.tgz", + "integrity": "sha512-qr4K6sPkwMFbQpMtMRv1Fsuzc3xXYyVmGzVdTBW/6NcFhEKfEq0Cz5kptxCeu3IZ83+FrZVsNi/5gyu428Bffw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "@toruslabs/eccrypto": "4.0.0", "ethers": "5.7.2", "secp256k1": "4.0.4", @@ -4751,14 +4853,14 @@ } }, "node_modules/@requestnetwork/request-logic": { - "version": "0.35.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/request-logic/-/request-logic-0.35.1-next.2088.tgz", - "integrity": "sha512-FP1dKlQHn/8HHIVmN3KgDOSy7ltobAbhkiB5GY6djIPCU9NR8voUpvNJVLxcRsW+e1/9oQduKHeF22X8F2QQvw==", - "dependencies": { - "@requestnetwork/advanced-logic": "0.44.1-next.2088+9af0ebdc", - "@requestnetwork/multi-format": "0.19.1-next.2088+9af0ebdc", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "version": "0.35.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/request-logic/-/request-logic-0.35.1.tgz", + "integrity": "sha512-6UGyMO7C8voT6rIrdwroKF6AG4/MZN35AKvCCX3DX/KIP7oOjRkLXlyCgWqxJoAikVHXNU28dt5ZWwDFHdQALQ==", + "dependencies": { + "@requestnetwork/advanced-logic": "0.45.0", + "@requestnetwork/multi-format": "0.19.1", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "semver": "7.5.4", "tslib": "2.5.0" }, @@ -4767,9 +4869,9 @@ } }, "node_modules/@requestnetwork/request-logic/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -4778,11 +4880,11 @@ } }, "node_modules/@requestnetwork/request-logic/node_modules/@requestnetwork/utils": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1-next.2088.tgz", - "integrity": "sha512-xTG362M7WaAWFiWpIGJpZnLWhfvSy5Z0jwp0jfK4/sGB14n0oEgbuItYDl/iev3j3e/galm5DiCoKg4N5JhbqQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1.tgz", + "integrity": "sha512-qr4K6sPkwMFbQpMtMRv1Fsuzc3xXYyVmGzVdTBW/6NcFhEKfEq0Cz5kptxCeu3IZ83+FrZVsNi/5gyu428Bffw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "@toruslabs/eccrypto": "4.0.0", "ethers": "5.7.2", "secp256k1": "4.0.4", @@ -4823,13 +4925,13 @@ } }, "node_modules/@requestnetwork/transaction-manager": { - "version": "0.36.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/transaction-manager/-/transaction-manager-0.36.1-next.2088.tgz", - "integrity": "sha512-+A+l0vgD+lcZFDZYqTnjMKNhL+jXLX7Iw+tBBXL+PrOPesM1WtY8zsgqgcp0uEVDRTJW0pB81CigvdKITogegg==", + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/transaction-manager/-/transaction-manager-0.36.1.tgz", + "integrity": "sha512-s3gt1qM2BudbX1IjZkzUBzFL9oNEpSH6/zmw/ZjGVtkevtfu28lfUScpm3ltJesTKsjFWSW6LOWb68ClLidcTg==", "dependencies": { - "@requestnetwork/multi-format": "0.19.1-next.2088+9af0ebdc", - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", - "@requestnetwork/utils": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/multi-format": "0.19.1", + "@requestnetwork/types": "0.45.1", + "@requestnetwork/utils": "0.45.1", "tslib": "2.5.0" }, "engines": { @@ -4837,9 +4939,9 @@ } }, "node_modules/@requestnetwork/transaction-manager/node_modules/@requestnetwork/types": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1-next.2088.tgz", - "integrity": "sha512-Ydc0Vbu7mePv+ZbK1gNE0x2pKBwxZbPUGYMZeEgqzTYojfRQPQdFfR+VZJ762HiI9E3ampIe1yxjjpyTCFXHcg==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/types/-/types-0.45.1.tgz", + "integrity": "sha512-cAaZPF2va29/Pjcucx3GqZ7yIlR0iRjmFxC4T5e0/ISONWxclH2nPY+MTYXpFjtNEwnOdnAJ65I8Dzb2FwNEKQ==", "dependencies": { "ethers": "5.7.2" }, @@ -4848,11 +4950,11 @@ } }, "node_modules/@requestnetwork/transaction-manager/node_modules/@requestnetwork/utils": { - "version": "0.45.1-next.2088", - "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1-next.2088.tgz", - "integrity": "sha512-xTG362M7WaAWFiWpIGJpZnLWhfvSy5Z0jwp0jfK4/sGB14n0oEgbuItYDl/iev3j3e/galm5DiCoKg4N5JhbqQ==", + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@requestnetwork/utils/-/utils-0.45.1.tgz", + "integrity": "sha512-qr4K6sPkwMFbQpMtMRv1Fsuzc3xXYyVmGzVdTBW/6NcFhEKfEq0Cz5kptxCeu3IZ83+FrZVsNi/5gyu428Bffw==", "dependencies": { - "@requestnetwork/types": "0.45.1-next.2088+9af0ebdc", + "@requestnetwork/types": "0.45.1", "@toruslabs/eccrypto": "4.0.0", "ethers": "5.7.2", "secp256k1": "4.0.4", @@ -9681,6 +9783,15 @@ "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", @@ -9948,6 +10059,39 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, + "node_modules/embla-carousel": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.3.1.tgz", + "integrity": "sha512-DutFjtEO586XptDn4cwvBJwsR/8fMa4jUk5Jk2g+/elKgu8mdn0Z2sx33g4JskvbLc1/6P8Xg4QlfELGJFcP5A==" + }, + "node_modules/embla-carousel-autoplay": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.3.1.tgz", + "integrity": "sha512-L8THF1AJJSQtlNa1wZ6lEKh/CiZssE3TsVFtabQNsS+pc1O1O/YTIYCC3khdQAztGMOBf3WfVDIY/4AIfQj3JQ==", + "peerDependencies": { + "embla-carousel": "8.3.1" + } + }, + "node_modules/embla-carousel-react": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.3.1.tgz", + "integrity": "sha512-gBY0zM+2ASvKFwRpTIOn2SLifFqOKKap9R/y0iCpJWS3bc8OHVEn2gAThGYl2uq0N+hu9aBiswffL++OYZOmDQ==", + "dependencies": { + "embla-carousel": "8.3.1", + "embla-carousel-reactive-utils": "8.3.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.3.1.tgz", + "integrity": "sha512-Js6rTTINNGnUGPu7l5kTcheoSbEnP5Ak2iX0G9uOoI8okTNLMzuWlEIpYFd1WP0Sq82FFcLkKM2oiO6jcElZ/Q==", + "peerDependencies": { + "embla-carousel": "8.3.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -14383,6 +14527,34 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", + "integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index f030ce7..dcca268 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rn-checkout", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", @@ -17,11 +17,15 @@ "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.2", - "@requestnetwork/payment-widget": "^0.3.1", + "@requestnetwork/payment-widget": "^0.3.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^4.1.0", + "embla-carousel-autoplay": "^8.3.1", + "embla-carousel-react": "^8.3.1", "framer-motion": "^11.3.28", "lucide-react": "^0.428.0", "next": "14.2.5", @@ -31,7 +35,8 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "validator": "^13.12.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zustand": "^5.0.1" }, "devDependencies": { "@types/node": "^20", diff --git a/src/app/(demo)/checkout/page.tsx b/src/app/(demo)/checkout/page.tsx new file mode 100644 index 0000000..4538e59 --- /dev/null +++ b/src/app/(demo)/checkout/page.tsx @@ -0,0 +1,10 @@ +import { CheckoutStepper } from "@/components/CheckoutStepper"; + +export default function CheckoutPage() { + return ( +
+

Checkout

+ +
+ ); +} diff --git a/src/app/(demo)/event/[id]/page.tsx b/src/app/(demo)/event/[id]/page.tsx new file mode 100644 index 0000000..392aedf --- /dev/null +++ b/src/app/(demo)/event/[id]/page.tsx @@ -0,0 +1,112 @@ +import { notFound } from "next/navigation"; +import Image from "next/image"; +import { CalendarIcon, MapPinIcon, Clock } from "lucide-react"; +import { format } from "date-fns"; +import { TicketSelector } from "@/components/TicketSelector"; +import eventsData from "@/const/data.json"; + +async function getEventById(id: string) { + // In a real app, this would be a DB or API call + const event = eventsData.events.find((event) => event.id === id); + if (!event) return null; + return event; +} + +export default async function EventDetailsPage({ + params, +}: { + params: { id: string }; +}) { + const event = await getEventById(params.id); + + if (!event) { + notFound(); + } + + return ( +
+ {/* Hero Section */} +
+ {event.name} +
+
+ + {event.type} + +

{event.name}

+
+
+ + {/* Content Section */} +
+
+ {/* Main Content */} +
+ {/* Event Details */} +
+

Event Details

+
+
+ + + {format(new Date(event.dateTime), "MMMM d, yyyy")} + +
+
+ + + {format(new Date(event.dateTime), "h:mm a")} -{" "} + {format(new Date(event.endDateTime), "h:mm a")} + +
+
+ + + {event.location.venue}, {event.location.city},{" "} + {event.location.country} + +
+
+
+ + {/* About Section */} +
+

About the Event

+

{event.organizer.description}

+
+ + {/* Organizer Section */} +
+

Organizer

+
+
+ {event.organizer.name} +
+
+

{event.organizer.name}

+

Event Organizer

+
+
+
+
+ + {/* Ticket Selection Sidebar */} +
+ +
+
+
+
+ ); +} diff --git a/src/app/(demo)/page.tsx b/src/app/(demo)/page.tsx new file mode 100644 index 0000000..e45f842 --- /dev/null +++ b/src/app/(demo)/page.tsx @@ -0,0 +1,66 @@ +import { EventShowcase } from "@/components/EventShowcase"; +import { + Carousel, + CarouselContent, + CarouselItem, +} from "@/components/ui/carousel"; +import eventData from "@/const/data.json"; +import Image from "next/image"; +import Link from "next/link"; + +export const metadata = { + title: "Request Checkout Demo", + description: + "This is a demo of the Request Checkout widget. It is a pre-built component that abstracts all the complexities of blockchain transactions using Request Network, making it simple for businesses to handle crypto-to-crypto payments without deep technical knowledge", +}; + +export default function DemoPage() { + const events = eventData.events; + const featuredEvents = events.filter((event) => event.featured); + + return ( + <> +
+ {/* Featured Events Carousel */} +
+ + + {featuredEvents.map((event) => ( + + +
+ {event.name} +
+
+
+ + {event.type} + +
+

{event.name}

+

Featured Event

+
+
+ + + ))} + + +
+ +
+ + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c3dbf59..a76c4be 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -49,7 +49,9 @@ export default function RootLayout({ - {children} +
+ {children} +
diff --git a/src/app/page.tsx b/src/app/playground/page.tsx similarity index 82% rename from src/app/page.tsx rename to src/app/playground/page.tsx index 5c34afe..a0f4c23 100644 --- a/src/app/page.tsx +++ b/src/app/playground/page.tsx @@ -1,9 +1,15 @@ import { Playground } from "@/components/Playground"; -export default function Home() { +export const metadata = { + title: "Request Checkout Playground", + description: + "A playground for the Request Checkout widget. You can experiment with the widget's properties, such as seller information, product details, and supported currencies.", +}; + +export default function PlaygroundPage() { return ( -
-

Request Checkout

+ <> +

Request Checkout Playground

@@ -46,6 +52,6 @@ export default function Home() {

-
+ ); } diff --git a/src/components/CartReview.tsx b/src/components/CartReview.tsx new file mode 100644 index 0000000..b55c9a8 --- /dev/null +++ b/src/components/CartReview.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useTicketStore } from "@/store/ticketStore"; +import { useEffect, useState } from "react"; + +export function CartReview() { + const { tickets, incrementQuantity, decrementQuantity, clearTickets } = + useTicketStore(); + const [total, setTotal] = useState(0); + + useEffect(() => { + const newTotal = Object.values(tickets).reduce( + (sum, ticket) => sum + ticket.price * ticket.quantity, + 0 + ); + setTotal(newTotal); + }, [tickets]); + + const groupedTickets = Object.entries(tickets).map(([key, ticket]) => ({ + eventId: key.split("-")[0], + ...ticket, + })); + + return ( +
+
+

Cart Review

+ {groupedTickets.length > 0 && ( + + )} +
+ + {groupedTickets.length === 0 ? ( +

Your cart is empty

+ ) : ( +
+
+ {groupedTickets.map((ticket) => ( +
+
+
+

{ticket.name}

+

+ ${ticket.price.toFixed(2)} +

+
+
+ + + {ticket.quantity} + + +
+
+
+ ))} +
+ +
+
+ Total: + + ${total > 0 ? total.toFixed(2) : "0.00"} + +
+
+
+ )} +
+ ); +} diff --git a/src/components/CheckoutStepper.tsx b/src/components/CheckoutStepper.tsx new file mode 100644 index 0000000..4b1f16a --- /dev/null +++ b/src/components/CheckoutStepper.tsx @@ -0,0 +1,89 @@ +"use client"; + +import { useState } from "react"; +import { CartReview } from "./CartReview"; +import { PaymentStep } from "./PaymentStep"; +import { useTicketStore } from "@/store/ticketStore"; + +const steps = [ + { id: "cart", title: "Cart Review" }, + { id: "payment", title: "Payment" }, +]; + +export function CheckoutStepper() { + const [currentStep, setCurrentStep] = useState("cart"); + const { tickets } = useTicketStore(); + + // Check if cart has items + const hasItems = Object.keys(tickets).length > 0; + + return ( +
+ {/* Stepper UI */} +
+ {steps.map((step, index) => ( +
+
+ {index + 1} +
+ + {step.title} + + {index < steps.length - 1 && ( +
+ )} +
+ ))} +
+ + {/* Step Content */} +
+ {currentStep === "cart" ? ( +
+ +
+ +
+
+ ) : ( +
+ +
+ +
+
+ )} +
+
+ ); +} diff --git a/src/components/EventShowcase.tsx b/src/components/EventShowcase.tsx new file mode 100644 index 0000000..1a9a5d2 --- /dev/null +++ b/src/components/EventShowcase.tsx @@ -0,0 +1,127 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { CalendarIcon, MapPinIcon } from "lucide-react"; +import { format } from "date-fns"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { useState, useMemo } from "react"; + +interface Event { + id: string; + name: string; + type: string; + image: string; + dateTime: string; + location: { + city: string; + country: string; + }; + startingPrice: number; +} + +interface EventShowcaseProps { + events: Event[]; +} + +export const EventShowcase = ({ events }: EventShowcaseProps) => { + const [activeTab, setActiveTab] = useState("all"); + + // Get unique event types and add "All" option + const eventTypes = useMemo(() => { + const types = ["All", ...new Set(events.map((event) => event.type))]; + return types; + }, [events]); + + // Filter events based on active tab + const filteredEvents = useMemo(() => { + if (activeTab.toLowerCase() === "all") return events; + return events.filter( + (event) => event.type.toLowerCase() === activeTab.toLowerCase() + ); + }, [events, activeTab]); + + return ( + + + {eventTypes.map((type) => ( + + {type} + + ))} + + + {eventTypes.map((type) => ( + +
+ {filteredEvents.map((event) => ( + +
+ {event.name} +
+ + {event.type} + +
+
+ +
+
+

+ {event.name} +

+ +
+
+ + + {format( + new Date(event.dateTime), + "MMM d, yyyy • h:mm a" + )} + +
+
+ + {`${event.location.city}, ${event.location.country}`} +
+
+
+ +
+
+ From + + ${event.startingPrice} + +
+ + View Details + +
+
+ + ))} +
+
+ ))} +
+ ); +}; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 296eaf1..e091826 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,6 +1,12 @@ +"use client"; + import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; export const Navbar = () => { + const pathname = usePathname(); + return (
diff --git a/src/components/PaymentStep.tsx b/src/components/PaymentStep.tsx new file mode 100644 index 0000000..c0a5868 --- /dev/null +++ b/src/components/PaymentStep.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useTicketStore } from "@/store/ticketStore"; +import PaymentWidget from "@requestnetwork/payment-widget/react"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; + +export function PaymentStep() { + const { tickets, clearTickets } = useTicketStore(); + const [total, setTotal] = useState(0); + const router = useRouter(); + + useEffect(() => { + const newTotal = Object.values(tickets).reduce( + (sum, ticket) => sum + ticket.price * ticket.quantity, + 0 + ); + setTotal(newTotal); + }, [tickets]); + + return ( +
+ {/* Order Summary */} +
+

Order Summary

+
+ {Object.values(tickets).map((ticket) => ( +
+
+

{ticket.name}

+

+ Quantity: {ticket.quantity} +

+
+

+ ${(ticket.price * ticket.quantity).toFixed(2)} +

+
+ ))} +
+ Total: + + ${total.toFixed(2)} + +
+
+
+ + {/* Payment Widget */} +
+

Payment

+ { + clearTickets(); + + setTimeout(() => { + router.push("/"); + }, 5000); + }} + hideTotalAmount + /> +
+
+ ); +} diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index f112054..f20ce51 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -17,7 +17,7 @@ import { Textarea } from "./ui/textarea"; import { ZERO_ADDRESS } from "@/lib/constants"; import { cn } from "@/lib/utils"; import { SectionHeader } from "./ui/section-header"; -import { Tabs } from "./ui/tabs"; +import { Tabs } from "./ui/custom-tabs"; export const Playground = () => { // Tabs diff --git a/src/components/TicketSelector.tsx b/src/components/TicketSelector.tsx new file mode 100644 index 0000000..e31fa3d --- /dev/null +++ b/src/components/TicketSelector.tsx @@ -0,0 +1,143 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useTicketStore } from "@/store/ticketStore"; +import Link from "next/link"; + +interface Location { + venue: string; + address: string; + city: string; + country: string; + coordinates: { + lat: number; + lng: number; + }; +} + +interface Organizer { + name: string; + logo: string; + description: string; +} + +interface TicketTier { + id: string; + name: string; + price: number; + description: string; + available: number; +} + +interface Event { + id: string; + name: string; + type: string; + featured: boolean; + image: string; + headerImage: string; + dateTime: string; + endDateTime: string; + location: Location; + startingPrice: number; + organizer: Organizer; + ticketTiers: TicketTier[]; +} + +interface TicketSelectorProps { + event: Event; +} + +export function TicketSelector({ event }: TicketSelectorProps) { + const { tickets, incrementQuantity, decrementQuantity } = useTicketStore(); + const [total, setTotal] = useState(0); + + useEffect(() => { + let newTotal = 0; + Object.entries(tickets).forEach(([key, ticket]) => { + if (key.startsWith(event.id)) { + newTotal += Number(ticket.price) * Number(ticket.quantity); + } + }); + setTotal(newTotal); + }, [tickets, event.id]); + + const getTicketQuantity = (ticketId: string) => { + const key = `${event.id}-${ticketId}`; + return tickets[key]?.quantity || 0; + }; + + return ( +
+

Select Tickets

+ +
+ {event.ticketTiers.map((tier) => ( +
+
+
+

{tier.name}

+

+ ${tier.price} +

+
+
+ + + {getTicketQuantity(tier.id)} + + +
+
+

{tier.description}

+
+ ))} +
+ +
+
+ Total: + + ${total > 0 ? total.toFixed(2) : "0.00"} + +
+ + + + +
+
+ ); +} diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx new file mode 100644 index 0000000..86a38a3 --- /dev/null +++ b/src/components/ui/carousel.tsx @@ -0,0 +1,280 @@ +"use client"; + +import * as React from "react"; +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; + +type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: "horizontal" | "vertical"; + setApi?: (api: CarouselApi) => void; + autoplay?: boolean; + interval?: number; +}; + +type CarouselContextProps = { + carouselRef: ReturnType[0]; + api: ReturnType[1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; + +const CarouselContext = React.createContext(null); + +function useCarousel() { + const context = React.useContext(CarouselContext); + + if (!context) { + throw new Error("useCarousel must be used within a "); + } + + return context; +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + autoplay = false, + interval = 5000, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return; + } + + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev(); + }, [api]); + + const scrollNext = React.useCallback(() => { + api?.scrollNext(); + }, [api]); + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault(); + scrollPrev(); + } else if (event.key === "ArrowRight") { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrev, scrollNext] + ); + + React.useEffect(() => { + if (!api || !setApi) { + return; + } + + setApi(api); + }, [api, setApi]); + + React.useEffect(() => { + if (!api) { + return; + } + + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); + + return () => { + api?.off("select", onSelect); + }; + }, [api, onSelect]); + + React.useEffect(() => { + if (!api || !autoplay) return; + + const autoplayInterval = setInterval(() => { + if (api.canScrollNext()) { + api.scrollNext(); + } else { + api.scrollTo(0); // Reset to first slide + } + }, interval); + + return () => clearInterval(autoplayInterval); + }, [api, autoplay, interval]); + + return ( + +
+ {children} +
+
+ ); + } +); +Carousel.displayName = "Carousel"; + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); + + return ( +
+
+
+ ); +}); +CarouselContent.displayName = "CarouselContent"; + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return ( +
+ ); +}); +CarouselItem.displayName = "CarouselItem"; + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); + + return ( + + ); +}); +CarouselPrevious.displayName = "CarouselPrevious"; + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel(); + + return ( + + ); +}); +CarouselNext.displayName = "CarouselNext"; + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +}; diff --git a/src/components/ui/custom-tabs.tsx b/src/components/ui/custom-tabs.tsx new file mode 100644 index 0000000..454b50f --- /dev/null +++ b/src/components/ui/custom-tabs.tsx @@ -0,0 +1,98 @@ +"use client"; + +import * as React from "react"; +import { cn } from "@/lib/utils"; + +interface TabsContextType { + activeTab: string; + setActiveTab: (value: string) => void; +} + +const TabsContext = React.createContext(undefined); + +interface TabsProps { + defaultValue: string; + onChange?: (value: string) => void; + children: React.ReactNode; + className?: string; +} + +interface TabListProps { + tabs: { + label: string; + value: string; + }[]; + className?: string; +} + +interface TabSectionProps { + value: string; + children: React.ReactNode; +} + +export const Tabs = ({ + defaultValue, + onChange, + children, + className, +}: TabsProps) => { + const [activeTab, setActiveTab] = React.useState(defaultValue); + + const handleTabChange = (value: string) => { + setActiveTab(value); + onChange?.(value); + }; + + return ( + +
{children}
+
+ ); +}; + +const TabList = ({ tabs, className }: TabListProps) => { + const context = React.useContext(TabsContext); + if (!context) throw new Error("TabList must be used within Tabs"); + + const { activeTab, setActiveTab } = context; + + return ( +
+
+ {tabs.map((tab) => ( + + ))} +
+
+ ); +}; + +const TabSection = ({ value, children }: TabSectionProps) => { + const context = React.useContext(TabsContext); + if (!context) throw new Error("TabSection must be used within Tabs"); + + const { activeTab } = context; + + if (activeTab !== value) return null; + + return
{children}
; +}; + +// Attach components to Tabs +Tabs.List = TabList; +Tabs.Section = TabSection; diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index 454b50f..4d030b1 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -1,98 +1,55 @@ -"use client"; - -import * as React from "react"; -import { cn } from "@/lib/utils"; - -interface TabsContextType { - activeTab: string; - setActiveTab: (value: string) => void; -} - -const TabsContext = React.createContext(undefined); - -interface TabsProps { - defaultValue: string; - onChange?: (value: string) => void; - children: React.ReactNode; - className?: string; -} - -interface TabListProps { - tabs: { - label: string; - value: string; - }[]; - className?: string; -} - -interface TabSectionProps { - value: string; - children: React.ReactNode; -} - -export const Tabs = ({ - defaultValue, - onChange, - children, - className, -}: TabsProps) => { - const [activeTab, setActiveTab] = React.useState(defaultValue); - - const handleTabChange = (value: string) => { - setActiveTab(value); - onChange?.(value); - }; - - return ( - -
{children}
-
- ); -}; - -const TabList = ({ tabs, className }: TabListProps) => { - const context = React.useContext(TabsContext); - if (!context) throw new Error("TabList must be used within Tabs"); - - const { activeTab, setActiveTab } = context; - - return ( -
-
- {tabs.map((tab) => ( - - ))} -
-
- ); -}; - -const TabSection = ({ value, children }: TabSectionProps) => { - const context = React.useContext(TabsContext); - if (!context) throw new Error("TabSection must be used within Tabs"); - - const { activeTab } = context; - - if (activeTab !== value) return null; - - return
{children}
; -}; - -// Attach components to Tabs -Tabs.List = TabList; -Tabs.Section = TabSection; +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/src/const/data.json b/src/const/data.json new file mode 100644 index 0000000..566f556 --- /dev/null +++ b/src/const/data.json @@ -0,0 +1,452 @@ +{ + "events": [ + { + "id": "tech-conf-2024", + "name": "Future of Tech Summit 2024", + "type": "Conference", + "featured": true, + "image": "https://images.unsplash.com/photo-1540575467063-178a50c2df87", + "headerImage": "https://images.unsplash.com/photo-1515187029135-18ee286d815b", + "dateTime": "2024-09-15T09:00:00", + "endDateTime": "2024-09-16T18:00:00", + "location": { + "venue": "Digital Innovation Center", + "address": "123 Tech Boulevard", + "city": "San Francisco", + "country": "USA", + "coordinates": { + "lat": 37.7749, + "lng": -122.4194 + } + }, + "startingPrice": 299, + "organizer": { + "name": "TechEvents Global", + "logo": "https://images.unsplash.com/photo-1599305445671-ac291c95aaa9", + "description": "Leading technology conference organizer since 2010" + }, + "ticketTiers": [ + { + "id": "regular", + "name": "Regular Access", + "price": 299, + "description": "Full conference access, lunch included", + "available": 200 + }, + { + "id": "vip", + "name": "VIP Experience", + "price": 599, + "description": "Premium seating, exclusive networking event, speaker meet & greet", + "available": 50 + }, + { + "id": "student", + "name": "Student Pass", + "price": 149, + "description": "Valid student ID required, full conference access", + "available": 100 + } + ] + }, + { + "id": "rock-festival-2024", + "name": "Summer Rock Festival", + "type": "Concert", + "featured": true, + "image": "https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3", + "headerImage": "https://images.unsplash.com/photo-1429962714451-bb934ecdc4ec", + "dateTime": "2024-07-20T16:00:00", + "endDateTime": "2024-07-20T23:00:00", + "location": { + "venue": "Sunset Arena", + "address": "789 Beach Road", + "city": "Miami", + "country": "USA", + "coordinates": { + "lat": 25.7617, + "lng": -80.1918 + } + }, + "startingPrice": 79, + "organizer": { + "name": "Rock Nation Events", + "logo": "https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4", + "description": "Premier music festival organizer" + }, + "ticketTiers": [ + { + "id": "general", + "name": "General Admission", + "price": 79, + "description": "Standing area access", + "available": 1000 + }, + { + "id": "vip-concert", + "name": "VIP Zone", + "price": 199, + "description": "Premium viewing area, complimentary drinks", + "available": 200 + }, + { + "id": "backstage", + "name": "Backstage Experience", + "price": 499, + "description": "Meet the artists, exclusive merchandise", + "available": 50 + } + ] + }, + { + "id": "design-workshop", + "name": "UX Design Masterclass", + "type": "Workshop", + "featured": true, + "image": "https://images.unsplash.com/photo-1531403009284-440f080d1e12", + "headerImage": "https://images.unsplash.com/photo-1558403194-611308249627", + "dateTime": "2024-08-05T10:00:00", + "endDateTime": "2024-08-05T17:00:00", + "location": { + "venue": "Creative Hub", + "address": "456 Design Street", + "city": "London", + "country": "UK", + "coordinates": { + "lat": 51.5074, + "lng": -0.1278 + } + }, + "startingPrice": 199, + "organizer": { + "name": "Design Masters", + "logo": "https://images.unsplash.com/photo-1557821552-17105176677c", + "description": "Expert-led design workshops and courses" + }, + "ticketTiers": [ + { + "id": "workshop-basic", + "name": "Workshop Access", + "price": 199, + "description": "Full day workshop, materials included", + "available": 30 + }, + { + "id": "workshop-plus", + "name": "Workshop + Mentoring", + "price": 299, + "description": "Workshop access plus 1-hour personal mentoring session", + "available": 15 + } + ] + }, + { + "id": "startup-conference", + "name": "Startup Growth Summit", + "type": "Conference", + "featured": false, + "image": "https://images.unsplash.com/photo-1475721027785-f74eccf877e2", + "headerImage": "https://images.unsplash.com/photo-1559223607-a43c990c692c", + "dateTime": "2024-10-10T08:30:00", + "endDateTime": "2024-10-11T17:00:00", + "location": { + "venue": "Innovation Hub", + "address": "321 Startup Avenue", + "city": "Berlin", + "country": "Germany", + "coordinates": { + "lat": 52.5200, + "lng": 13.4050 + } + }, + "startingPrice": 249, + "organizer": { + "name": "Startup Network EU", + "logo": "https://images.unsplash.com/photo-1559223607-a43c990c692c", + "description": "Europe's leading startup community" + }, + "ticketTiers": [ + { + "id": "startup-regular", + "name": "Regular Access", + "price": 249, + "description": "Full conference access, networking lunch", + "available": 300 + }, + { + "id": "startup-premium", + "name": "Premium Access", + "price": 449, + "description": "VIP seating, investor meeting opportunities", + "available": 100 + }, + { + "id": "startup-founder", + "name": "Founder Package", + "price": 699, + "description": "Premium access, pitch session slot, mentor matching", + "available": 50 + } + ] + }, + { + "id": "jazz-night", + "name": "Evening of Jazz", + "type": "Concert", + "featured": false, + "image": "https://images.unsplash.com/photo-1415201364774-f6f0bb35f28f", + "headerImage": "https://images.unsplash.com/photo-1511192336575-5a79af67a629", + "dateTime": "2024-06-30T19:00:00", + "endDateTime": "2024-06-30T23:00:00", + "location": { + "venue": "Blue Note Club", + "address": "567 Jazz Street", + "city": "New Orleans", + "country": "USA", + "coordinates": { + "lat": 29.9511, + "lng": -90.0715 + } + }, + "startingPrice": 59, + "organizer": { + "name": "Jazz & Soul Productions", + "logo": "https://images.unsplash.com/photo-1415201364774-f6f0bb35f28f", + "description": "Authentic jazz experiences since 1990" + }, + "ticketTiers": [ + { + "id": "jazz-standard", + "name": "Standard Seating", + "price": 59, + "description": "General admission seating", + "available": 100 + }, + { + "id": "jazz-premium", + "name": "Premium Table", + "price": 129, + "description": "Front row table service, complimentary drink", + "available": 40 + }, + { + "id": "jazz-dinner", + "name": "Dinner Package", + "price": 189, + "description": "Premium seating with 3-course dinner", + "available": 30 + } + ] + }, + { + "id": "eth-global-2025", + "name": "ETHGlobal Summit", + "type": "Conference", + "featured": true, + "image": "https://images.unsplash.com/photo-1639762681485-074b7f938ba0", + "headerImage": "https://images.unsplash.com/photo-1642006953663-06f0387f5652", + "dateTime": "2025-03-15T09:00:00", + "endDateTime": "2025-03-17T18:00:00", + "location": { + "venue": "Crypto Convention Center", + "address": "888 Blockchain Boulevard", + "city": "Singapore", + "country": "Singapore", + "coordinates": { + "lat": 1.3521, + "lng": 103.8198 + } + }, + "startingPrice": 399, + "organizer": { + "name": "ETHGlobal", + "logo": "https://images.unsplash.com/photo-1622630998477-20aa696ecb05", + "description": "Leading Ethereum ecosystem event organizer" + }, + "ticketTiers": [ + { + "id": "builder", + "name": "Builder Pass", + "price": 399, + "description": "Full conference access, hackathon participation, workshops", + "available": 1000 + }, + { + "id": "investor", + "name": "Investor Connect", + "price": 1499, + "description": "VIP access, private networking lounge, pitch sessions", + "available": 100 + } + ] + }, + { + "id": "nft-art-festival", + "name": "Digital Renaissance: NFT Art Festival", + "type": "Exhibition", + "featured": false, + "image": "https://images.unsplash.com/photo-1620641788421-7a1c342ea42e", + "headerImage": "https://images.unsplash.com/photo-1638913662380-9799def8ffb1", + "dateTime": "2025-05-20T10:00:00", + "endDateTime": "2025-05-23T20:00:00", + "location": { + "venue": "Modern Art Gallery", + "address": "456 Digital Avenue", + "city": "Tokyo", + "country": "Japan", + "coordinates": { + "lat": 35.6762, + "lng": 139.6503 + } + }, + "startingPrice": 89, + "organizer": { + "name": "CryptoArt Collective", + "logo": "https://images.unsplash.com/photo-1620641788421-7a1c342ea42e", + "description": "Bridging traditional and digital art" + }, + "ticketTiers": [ + { + "id": "gallery-pass", + "name": "Gallery Access", + "price": 89, + "description": "Exhibition access, digital catalog", + "available": 500 + }, + { + "id": "collector", + "name": "Collector's Pass", + "price": 299, + "description": "VIP preview, artist meetups, exclusive NFT drop", + "available": 100 + } + ] + }, + { + "id": "food-wine-fest", + "name": "Mediterranean Food & Wine Festival", + "type": "Festival", + "featured": false, + "image": "https://images.unsplash.com/photo-1555396273-367ea4eb4db5", + "headerImage": "https://images.unsplash.com/photo-1507434965515-61832950c743", + "dateTime": "2025-06-10T12:00:00", + "endDateTime": "2025-06-12T22:00:00", + "location": { + "venue": "Coastal Gardens", + "address": "123 Seaside Promenade", + "city": "Barcelona", + "country": "Spain", + "coordinates": { + "lat": 41.3851, + "lng": 2.1734 + } + }, + "startingPrice": 129, + "organizer": { + "name": "Mediterranean Culinary Arts", + "logo": "https://images.unsplash.com/photo-1466637574441-749b8f19452f", + "description": "Celebrating Mediterranean cuisine and culture" + }, + "ticketTiers": [ + { + "id": "tasting-pass", + "name": "Tasting Pass", + "price": 129, + "description": "All tastings, standard wine pairings", + "available": 1000 + }, + { + "id": "chef-experience", + "name": "Chef's Table Experience", + "price": 399, + "description": "Premium tastings, cooking workshops, exclusive wine tastings", + "available": 150 + } + ] + }, + { + "id": "defi-summit-2025", + "name": "DeFi Innovation Summit", + "type": "Conference", + "featured": false, + "image": "https://images.unsplash.com/photo-1639762681485-074b7f938ba0", + "headerImage": "https://images.unsplash.com/photo-1642006953663-06f0387f5652", + "dateTime": "2025-09-05T09:00:00", + "endDateTime": "2025-09-07T18:00:00", + "location": { + "venue": "Dubai Financial Center", + "address": "Sheikh Zayed Road", + "city": "Dubai", + "country": "UAE", + "coordinates": { + "lat": 25.2048, + "lng": 55.2708 + } + }, + "startingPrice": 599, + "organizer": { + "name": "DeFi Alliance", + "logo": "https://images.unsplash.com/photo-1622630998477-20aa696ecb05", + "description": "Advancing decentralized finance innovation" + }, + "ticketTiers": [ + { + "id": "defi-standard", + "name": "Standard Access", + "price": 599, + "description": "Full conference access, networking events", + "available": 500 + }, + { + "id": "defi-whale", + "name": "Whale Pass", + "price": 2499, + "description": "VIP access, private investment sessions, founder dinner", + "available": 50 + } + ] + }, + { + "id": "wayne-charity-gala", + "name": "Wayne Enterprises Annual Charity Gala", + "type": "Gala", + "featured": true, + "image": "https://images.unsplash.com/photo-1519167758481-83f550bb49b3", + "headerImage": "https://images.unsplash.com/photo-1492684223066-81342ee5ff30", + "dateTime": "2025-10-31T19:00:00", + "endDateTime": "2025-11-01T02:00:00", + "location": { + "venue": "Wayne Manor", + "address": "1007 Mountain Drive", + "city": "Gotham", + "country": "USA", + "coordinates": { + "lat": 40.7128, + "lng": -74.0060 + } + }, + "startingPrice": 1000, + "organizer": { + "name": "Wayne Foundation", + "logo": "https://images.unsplash.com/photo-1481819613568-3701cbc70156", + "description": "Serving Gotham's community since 1939" + }, + "ticketTiers": [ + { + "id": "gala-standard", + "name": "Standard Admission", + "price": 1000, + "description": "Evening gala access, dinner included (mask required)", + "available": 300 + }, + { + "id": "gala-vip", + "name": "VIP Experience", + "price": 5000, + "description": "Private lounge access, butler service, potential Batman sighting not guaranteed", + "available": 50 + } + ] + } + ] +} diff --git a/src/store/ticketStore.ts b/src/store/ticketStore.ts new file mode 100644 index 0000000..6a0b012 --- /dev/null +++ b/src/store/ticketStore.ts @@ -0,0 +1,69 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +interface TicketTier { + id: string; + name: string; + price: number; + description: string; + available: number; +} + +interface StoredTicket { + id: string; + name: string; + price: number; + quantity: number; +} + +interface TicketStore { + tickets: Record; + incrementQuantity: (key: string, ticket: TicketTier) => void; + decrementQuantity: (key: string) => void; + clearTickets: () => void; +} + +export const useTicketStore = create()( + persist( + (set) => ({ + tickets: {}, + incrementQuantity: (key: string, ticket: TicketTier) => + set((state) => { + const ticketExists = state.tickets[key]; + + return { + tickets: { + ...state.tickets, + [key]: { + id: key, + quantity: ticketExists ? ticketExists.quantity + 1 : 1, + price: ticket.price, + name: ticket.name, + }, + }, + }; + }), + decrementQuantity: (key: string) => + set((state) => { + if (state.tickets[key].quantity === 1) { + const { [key]: _, ...remainingTickets } = state.tickets; + return { tickets: remainingTickets }; + } + + return { + tickets: { + ...state.tickets, + [key]: { + ...state.tickets[key], + quantity: state.tickets[key].quantity - 1, + }, + }, + }; + }), + clearTickets: () => set({ tickets: {} }), + }), + { + name: "ticket-storage", + } + ) +); diff --git a/tsconfig.json b/tsconfig.json index 7b28589..151d648 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,16 +11,17 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", + "target": "es2015", "incremental": true, "plugins": [ { - "name": "next" - } + "name": "next", + }, ], "paths": { - "@/*": ["./src/*"] - } + "@/*": ["./src/*"], + }, }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules"], }